React學習路徑快速進入AntDesignPro開發

  好久沒有寫博客,有空再來記一下。最近在整些小東西,需要用到前端,最開始本着對nodejs的動不動幾百兆插件的恐懼, 於是使用自己以前寫的 OSS.Pjax 小框架(類似國外的Pjax,利用pushState達到單頁的效果),表單配合jQuery湊合一下,有興趣的可以看下我github上的OSS.Core.AdminUI 。不過本着學習精神,還是額外挑戰了下React(主要是衝着AntDesignPro去的,公司團隊已經在使用),結果這麼稍微深入瞭解一下,還真沒能逃掉真香定律。這篇文章主要借我個人經驗,梳理前後學習過程涉及知識點,希望可以幫助有需要的同學,特別是一直奮戰在服務端但希望瞭解前端的同學。

  1. 基礎相關涉及知識

  2. React及相關擴展框架認識

  3. AntDesign 相關開發框架

  4. Umi下AntDesignPro項目配置

  5. .NetCore項目下集成調試

  在開始下邊之前,先貼一個涉及相關框架的圖:

     

一. 基礎相關涉及知識

  碰上新框架,不少同學的學習路徑直接就從入門到放棄了。這裏讓我們首先在戰略上輕視它,畢竟只是一個交互界面實現工具,萬變不離其宗,還是要落地到CSS,HTML,JS這些基礎實現上。重點在於,這個工具在這些基礎實現之上做了哪些改進,爲了這些改進引入了哪些新的支撐。首先基礎層面的認知必須清晰。

  這裏我們先關注下JS相關概念,有些名稱(如:ES5, ES6, ES2016)可能上來就把很多人整蒙了。這些和JS有何關聯,我們先來梳理一下。JS最早隨網景瀏覽器發佈,後來交由一個ECMA的組織制定統一標準,其他的瀏覽器依此提供js支持。ES5、ES6就是這些標準的版本,其定義了JS的相關語法標準規範。粗暴點理解,一個標準規範,一個規範的具體實現。特別是後邊的TypeScript,在語法上自然是實現了相關標準,於此同時它又引入了類、接口、泛型等其他高級語言特性,特別是類型檢查特性等,使得代碼更加可控和模塊化,所以它又稱爲JS的超集,因爲瀏覽器支持的是JS規範,所以TypeScript最終編譯成JS代碼運行。

  如果你還沒有使用過TypeScript,不必急於學習,後續繼續使用JS也是OK的。如果你想去學習瞭解,也挺簡單,不容易理解的可能集中在部分複雜的類型處理,這塊和靜態語言最大的不同就是類型結構是可以動態創建的,也可以瞭解下它本身提供的Pick,Exclude,Omit 幾個高級類型的使用。

  前端歷史悠久,除了JS,還有CSS、HTML也有了長足發展,以至於橫跨不同瀏覽器,上下不同版本,能夠做到的支持各不相同。這麼多新特性使用起來給開發人員帶來相當的壓力,開發過程中畏手畏腳,多少同學爲了兼容而身心疲憊。還好有那麼一羣好心人做了各種兼容處理類庫,這些包對新特性,新語法進行包裝或轉換,使得最終的代碼能夠在低版本的瀏覽器中運行。比如Babel,通過編譯,生成新的低版本代碼。當項目中使用了很多類庫之後,相互間的引用依賴,編譯順序,生成代碼壓縮等又成爲新的問題,於是我們又引入了Webpact這類用來打包的工具類庫,通過各種配置用來處理打包壓縮等處理。

  有了這些工具類庫,給了我們能夠使用新特性的機會,又儘可能做到向下的兼容支持。作爲類庫必須能夠廣泛傳播複用,肯定需要一個統一可靠的管理平臺,這時就引入了Nodejs的npm包管理器,Nodejs本身不過多說明,我們先了解它就是一個能夠讓JS脫離瀏覽器直接運行的環境,npm就是在集成在這個環境中包的上傳下載管理等命令引擎,類似.Net 的Nuget,Java的Maven。有了這個管理器,我們可以在項目中方便的通過npm命令安裝或運行對應的類庫包。

二. React及相關擴展框架認識

 1. React

 具體語法等請參見官方文檔,我們先認識其重要的核心組成部分。

  首先,我們要建立一個基礎認知,React也只是一個JS類庫,個人理解其核心包含兩大塊:

 1.1 語法層面

  通過對JS進行擴展的JSX語法,完成對UI交互的包裝渲染,類似Babel,這些語法最終通過解釋器轉換成瀏覽器可執行的基礎代碼。

  有了JSX語法,我們可以像處理模板一樣,完成變量等和Html元素的交互(如果不好理解,就看成一段html,將變動的地方打上個標記,調用時進行替換,最終不同變量生成的html不同),當然JSX並不是一個模板引擎,它是通過語法糖的形式達到了這樣的效果,通過這個語法編寫的代碼呈現形式上又類似Html元素。雖然類似Html元素,但又有了動態的變化能力,我們可以對這些代碼其抽象成可複用的模塊,也就是組件,這些組件通過暴露的屬性接收外界的變化,也就是props的這個東西,組件就通過props獲知個屬性的變化值,完成和組件外部的互動。

  在這裏特別說明一下,針對React的組件,裏面又分了有狀態(class定義)和無狀態(function定義)組件,我們先簡單瞭解就好,後續你會發現通過React Hooks的引入使用無狀態組件基本都可以解決,重要的是我們需要理解狀態(State)這個東西

 1.2  是圍繞State的作用範圍和生命週期。

  有了元素組件,也就有了可以通過控制參數來完成對dom元素操作完成頁面變化的能力,但如何和數據進行連接,這時我們就引入狀態的概念,也就是State對象。一旦State狀態對象發生變化(setState方法觸發),對應組件就會進行重新渲染,以完成數據對頁面的驅動。當然在這個渲染過程中,React內部進行了很多優化,比如通過虛擬dom樹的對比,只有真正變化的元素纔會重新渲染,儘可能的提升和保證性能。

  這裏需要說明React和Vue、Angular的一個重要不同:State的數據和元素的綁定是單向的,State的變化會驅動頁面元素變化,但頁面輸入框等的值變化並不會直接影響到State。單向還是雙向各有優劣,React的作者們已經做出了選擇,我們不必糾結。

  因爲State是一個狀態對象,就會存在一個作用的上下文範圍和生命週期管理,否則相互覆蓋或者常駐內存就會引發各種未知問題。

  a. 關於作用範圍:

  上邊說了組件和外部的交互通過Props可以進行,本着職責統一,State就不需要參與外邊的事情,專職服務於組件內部即可。這樣組件內部數據管理在State中,外部通過Props傳入,多層級嵌套引用時相互不會造成數據污染。

  組件狀態發生變化,組件本身及相關聯的子組件接收到變化完成變動,這種層層的嵌套,再加上State在數據綁定的單向特性,數據就像一個金子塔一樣由塔尖傳遞到塔底。所謂的React數據在組件層面是向下流動的就容易理解了。(當然父組件可以傳遞方法給子組件,子組件事件執行時觸發對父級組件的回調)

  b. 關於生命週期

  瞭解了狀態的作用範圍,我們就可以很好理解它的生命週期了,因爲它僅需服務組件本身,隨着組件渲染卸載一起生存即可。

  以此爲依據我們可以很輕易的理解一下兩個流程

  組件新建生命過程:1. 初始化構造組件 =》 2. 獲取父組件傳入的Props =》3. 初始化狀態對象 =》 4.準備渲染 =》 5. 實際渲染呈現 =》6. 渲染頁面完成

  組件更新生命過程:1. 接收父組件傳入的Props  =》2. 組件更新判斷(如果需要繼續) =》 3. 準備更新 =》 4. 實際渲染呈現 =》 5. 更新組件完成

  更新過程中如果狀態變化來自自身,跳過第一步。除了上邊的兩個流程,還有一個獨立的事件,就是組件無需展示時卸載事件。這裏不具體列出對應過程的方法名稱,沒有意義,我們主要了解處理的過程。特別是在後續的真正使用時,我們不會有太多機會需要直接操作相關事件。  

  一個簡單的組件定義示例:   

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

  學習地址:https://react.docschina.org/docs/getting-started.html

  2. React Hooks

   原本React本身已經提供了語法和狀態管理機制,但是這些本身相對基礎,特別是生命週期的變化配合狀態的變化。比如首次 組件加載完成後 通過Ajax加載了部分數據,父級組件變化,導致當前組件更新,但是調用的是 更新組件方法,那就需要在這兩個方法裏都去實現一遍Ajax請求。ReactHook的引入,對React本身基礎部分進行再次包裝。用最簡單的語句,完成原本複雜的實現。 讓我們可以在很大程度上不在需要關心生命週期的具體事件定義,不需要關心類組件的狀態初始化等處理。

  在最新的React版本中已經包含了Hooks相關實現,說兩個重要的 Hook實現:

  2.1 State Hook

  這個主要就是對狀態的包裝,原本需要聲明Class組件,構造函數中聲明State對象纔行,歷史寫法我們可以不再關心,當前實現示例如下:

import React, { useState } from 'react';

function Example() {
  // 聲明一個叫 "count" 的 state 變量
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

  2.2 Effect Hook

  這個主要是對聲明週期事件的包裝,上邊我已經列出生命週期中幾個過程,其中我們開發過程中經常可能涉及數據加載處理集中在:初始渲染加載完成, 組件更新渲染完成,組件卸載  這三步中,EffectHook 主要就是對這三者的組合封裝。

  歷史寫法我們無需關心,最新寫法如下:

import React, { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);
 // 數據變化時,自動修改頁面 title
 // 初始化和更新完成時,會自動調用
useEffect 傳入的方法 FuncA

 // 如果傳入的方法內部又返回了一個方法FuncB, 則 FuncB 會在組件卸載時執行
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

  學習地址:https://react.docschina.org/docs/hooks-intro.html

 3. ahooks 

 這個框架以前叫Umi Hooks,現在叫AHooks(應該是定位爲獨立的通用框架),是阿里團隊出的,主要是在 React Hooks 之上的擴展,包含了界面處理,到網絡請求等各種常用操作,比如我們想實時獲取頁面滾動信息, 示例如下:

import React from 'react';
import { useScroll } from 'ahooks';
export default () => { const scroll = useScroll(document); return ( <div> <div>{JSON.stringify(scroll)}</div> </div> ); };

  學習地址:https://ahooks.js.org/

 4. React Router

  React 的路由類庫,前面我們已經有了相關組件的定義,還侷限在頁面內。一個站點 Url 地址肯定不止一個,訪問不同的頁面URL地址,如何加載不同的組件 ,就交由這個類庫來處理。

  React Router的職責就是拿到頁面瀏覽器地址,完成對應的組件加載,或者是組件替換時,反向修改當前頁面瀏覽器地址。如果有興趣可以深入研究,否則瞭解大概用法即可,相對簡單,示例如下:

React.render((
  <Router>
    <Route path="/" component={App}>
      <IndexRoute component={Dashboard} />
      <Route path="about" component={About} />
    </Route>
  </Router>
), document.body)

  5. 其他類庫

  這裏簡單列幾個其他React擴展類庫,這些當時浪費了我一些時間,其實完全可以跳過,這些基本出現在React Hook出現之前,爲了完成一些複雜頁面處理。

  Redux,這個主要是用來管理狀態,用來解決相關變化和異步的問題。試想一個State,涉及多個異步加載組裝,以及相關組裝過程中的數據再加工,還是挺複雜的。

  Redux-Saga,主要是在Redux之上的一個異步相關封裝,無它,就是搞得更復雜了。

  DvaJS , 這個是阿里ant團隊出品,還是在Redux擴展的,沒啥意義了

  瞭解以上,基本上對React的輪廓有了一個大概的認知,具體語法使用時翻閱文檔即可。下一步我們就可以進入到業務開發環節,在實際的業務開發中我們都會遇到很多有共性且重複的組合模塊,在JQuery時代我們有Bootstrap,接下來我們介紹下在React下阿里的AntDesign 相關框架。

三. Ant Design 相關開發框架

 1. AntDesign(https://ant.design/

  這個是阿里團隊在React基礎上,封裝的業務開發常用 UI組件類庫列表,包括按鈕,佈局,導航等等,具體組件請參閱官方文檔。這些僅僅是一個個獨立的組件,不涉及具體的頁面。

 2. AntDesignPro(https://pro.ant.design/

  這個框架是AntDesign 的升級,可以理解爲就是一個後臺模板框架,通過對AntDesign 這些組件庫的包裝,提供了統一樣式,基礎佈局,列表頁,詳情頁.... 等這一類通用模板頁面。 它內部又細分出幾個獨立模塊,如:佈局(ProLayout),列表(ProTable)

 3. UmiJS(https://umijs.org/

  這個框架可以說是我們後續開發的核心了,這個框架包含兩個部分

  首先,從開發工具上:

  通過它,我們可以直接創建 Ant Design Pro 應用項目,其內部集成了babel, webpact等相關編譯打包工具,並已經進行了默認配置,無需我們對下層過多關心,除非你有相關特殊需求,可以開啓相關手動配置。

  同時,我們也可以通過編寫模擬接口,以及單元測試等。

  其次在應用層面:

  它內部集成了路由(React Router)等相關處理,我們可以很方便的通過配置的方式書寫路由和組件規則,以及頁面的跳轉(Link)等處理。

  同時也提供了部分應用插件(集成了ahooks )功能,如:網絡請求,國際化等

四. Umi下 Ant Design Pro項目配置

  上邊是整個相關框架輪廓和職責,這一節我們主要開始真正開始通過UmiJS, 進行AntDesignPro的開發。

  首先,我們需要安裝Nodejs環境,因爲npm包管理連接的外網,我們添加國內的包源,並使用cnpm命令代替:

# 國內源
npm i -g cnpm --registry=https://registry.npm.taobao.org

  在指定的文件夾中創建項目命令:

cnpm create umi

  之後會讓你選擇項目類型,我們選擇 ant-design-pro類型即可(同時會讓你選擇開發語言,如果不熟悉TypeScript,可以選擇JS)。

  創建項目之後,基本就能看到項目組成了,很簡單的結構。之後我們只需要執行即可運行查看效果:

cnpm install
cnpm run start

  這裏我重點說下核心入口頁面 src/app.ts  下的幾個配置(所有相關配置基本都在UmiJS框架中,可查閱相關文檔):

  1. 網絡請求全局化配置,如:

export const request: RequestConfig = {
  headers: {'Content-Type': 'application/json',
  },
  errorConfig: {
    adaptor: (resData, { res }) => {
      if (res.status != 200 && !resData.errorMessage) {
        const errorMessage = codeMessage[res.status] || res.statusText;
        return {
          ...resData,
          errorMessage,
        };
      }
      return resData;
    },
  }
};
// 參考:https://umijs.org/zh-CN/plugins/plugin-request

  2. getInitialState,這個方法會在第一次初始化時進行執行,如:

// src/app.ts
export async function getInitialState() {
  const data = await fetchXXX();
  return data;
}
// 參考:https://umijs.org/zh-CN/plugins/plugin-initial-state#getinitialstate

  3. 配置頁面佈局, 如:

// src/app.js
export const layout = { 
  logout: () => {}, // do something 
  rightRender:(initInfo)=> { return 'hahah'; },// return string || ReactNode; 
};
// 參考:https://umijs.org/zh-CN/plugins/plugin-layout

五. Net Core項目下集成調試

  如果你直接通過VS創建.Net Core的React項目,你會發現在項目文件下,會存在以下節點:

<Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
    <!-- Ensure Node.js is installed -->
    <Exec Command="node --version" ContinueOnError="true">
      <Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
    </Exec>
    <Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." />
    <Message Importance="high" Text="Restoring dependencies using 'npm'. This may take several minutes..." />
    <Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
  </Target>
  
  <Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
    <Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
    <Exec WorkingDirectory="$(SpaRoot)" Command="npm run build" />
    <ItemGroup>
      <DistFiles Include="$(SpaRoot)build\**" />
      <ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
        <RelativePath>%(DistFiles.Identity)</RelativePath>
        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
        <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
      </ResolvedFileToPublish>
    </ItemGroup>
  </Target>

  在Startup.cs 文件下存在:

public void ConfigureServices(IServiceCollection services)
{
  // ...其他代碼
services.AddSpaStaticFiles(configuration => { configuration.RootPath = "ClientApp/build"; }); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
  // ... 其他代碼 app.UseSpa(spa => { spa.Options.SourcePath = "ClientApp"; if (env.IsDevelopment()) { spa.UseReactDevelopmentServer(npmScript: "start"); } }); }

   也就是每次生成時都會執行 (npm install),在調試的時候通過Startup.cs 的代碼執行(npm start),但是npm start比較耗費時間,每次調試都運行一次(甚至在頁面無修改的情況下),及其的不便利,特別是AntDesignPro相關的包特別多,極容易卡死。

 這裏我們只需要正常創建Asp.Net Core 項目(不要選擇React),前端部分放在.Net Core項目文件夾之外,在外部通過VS code開發, 需要調試時 ,直接通過.Net Core 中提供的代理的方式進行,在Startup中的方法如下:

public void ConfigureServices(IServiceCollection services)
{
  // ...其他代碼
    services.AddSpaStaticFiles(configuration =>
    {
        configuration.RootPath = "ClientApp/build";
    });
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
  // ... 其他代碼
    app.UseSpa(spa =>
    {
        spa.Options.SourcePath = "ClientApp";

        if (env.IsDevelopment())
        {
            spa.UseProxyToSpaDevelopmentServer("http://localhost:8000");
        }
    });
}

  這樣既保證了項目文件的乾淨,又能夠保證.net core 項目的快速調試運行,在發佈時,只需要將 前端項目的文件發佈到.Net Core 項目發佈目錄下的“ClientApp/build”文件夾中即可。

 

如果你已經看到這裏,並且感覺還行的話可以在下方點個贊,或者也可以關注我的公總號(見二維碼) 

_________________________________________

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章