好久沒有寫部落格,有空再來記一下。最近在整些小東西,需要用到前端,最開始本著對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>
);
};
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”資料夾中即可。
如果你已經看到這裡,並且感覺還行的話可以在下方點個贊,或者也可以關注我的公總號(見二維碼)
_________________________________________