為了防止被打,有請“燕雙鷹”鎮樓????♀?????????...o...
話說新冠3年,“狀態管理框架”豪杰并起、群雄逐鹿,ReduxToolkit、Mobx、Vuex、Pinia、Dva、Rematch、Recoil、Zustand、Mirror...敢問英雄獨鐘哪廂?
Flux狀態管理
筆者也用過很多型管理框架,大部分都是Flux框架的變種,只不過加上了一些自己的糖衣和輔助方法,
- ?? 只要糖衣做得好,省時省力人人要!
- 后面隨著
Typescript的普及,自動型別推斷也是狀態管理框架易用性的重要指標,
我們先簡單回顧幾款最主流的Flux狀態管理框架的寫法:
//基于Redux的Dva:
{
state(){
return {curUser: null}
},
reducers: {
setUser(state, {payload}) {
return {...state, curUser: payload}
},
},
effects: {
*login({ payload: {username, password} }, { put, call }){
const { data } = yield call(api.login, username, password);
yield put({ type: 'setUser', payload: data }); //無TS型別提示
}
}
};
//Vuex:
{
state(){
return {curUser: null}
},
mutations: {
setUser(state, curUser) {
state.curUser = curUser;
}
},
actions: {
async login({ commit }, {username, password}) {
const { data } = await api.login(username, password);
commit('setUser', data) //無TS型別提示
}
}
}
//Pinia:
{
state(){
return {curUser: null}
},
actions: {
setUser(curUser) {
this.curUser = curUser;
}
async login(username, password) {
const { data } = await api.login(username, password);
this.setUser(data) //有TS型別提示
}
}
}
果然都是一個媽生的,本質上無非就是玩3個概念:
- State
- 同步Action
- 異步Action
為Flux再添一把火??
既然都是玩這3個概念,大家都容易理解,那么要自薦的Elux就要閃亮登場了:
- Elux官網:https://eluxjs.com
- Github:https://github.com/hiisea/elux
先看它的基本用法:
class Model{
onMount() {
//初始賦值State
this.dispatch(this.actions._initState({curUser: null}));
}
@reducer //類似Vuex的mutations
setUser(curUser) {
//react中必需回傳一個新state
//return {...this.state, curUser};
this.state.curUser = curUser;
}
@effect() //類似Vuex的action
async login(username, password) {
const { data } = await api.login(username, password);
await this.dispatch(this.actions.setUser(data));
this.getRouter().relaunch({url: HomeUrl});
}
}
- onMount:初始化鉤子,在其中完成State的初始賦值,
- reducer:React系很容易理解,Vue系可以理解為mutation,它是改變State的唯一途徑,
- effect:React系很容易理解,Vue系可以理解為action,它是異步Action,
所以從糖衣語法來說Elux其實與Dva/Vuex/Pinia也差不多,不同在于:
- Elux使用
Decorator裝飾器語法來定義reducer(mutation)和effect(action),這樣更簡潔, - Elux使用
Class來組織Model,有2點好處:- 可以通過類的繼承和多型來復用公共邏輯,
- 可以通過TS的類成員權限(public/private/protected)來更好的封裝,
Elux特性
除了糖衣語法,Elux還有其更深層次的創新:
從圖中可以看出:
- store中保存了所有state
- 每個Model管理store下的一個節點
- view從store中獲取state
- dispatch(action)是觸發reducer/effect的唯一途徑
- reducer是純函式,也是修改state的唯一途徑
- effect可以處理任何異步操作,但不能直接修改state
- 一個action的派發類似于事件,可以觸發多個reducer和effect監聽
- view/effect/router都可以派發action
自動生成Action
這點類似于Pinia,不需要手動盲寫類似于{type:"xxx.xxx",payload:xxxx}這樣的Action結構體,而是通過方法自動生成:
const loginAction = stageActions.login('admin','123456');
//等于{type: 'user.login', payload:{username:'admin', password:'123456'}}
dispatch(loginAction);
且具備完美的TS型別提示:
模塊化
Elux使用微模塊來組合應用,每個微模塊對應一個業務模型Model,每個Model使用reducer/effect來維護Store下的一個節點ModuleState,
微模塊是一種前端業務模塊化方案,至此不引申開來,可參見我的發文【微模塊-前端業務模塊化探索,拆解巨石應用的又一利器】
事件化
將action當做Model中的事件,將reducer、effect當做Handler,這意味著dispatch(action)可以觸發多個reducer和effect,
通過事件總線機制,在保持各Model松散性的同時,加強Model之間的協同互動,舉個例子:
假設有3個模塊:user(用戶模塊)、article(文章模塊)、my(個人中心模塊)
當用戶登錄時,article(文章模塊)需要將狀態修改為可編輯,my(個人中心模塊)需要獲取最新通知
user/model.ts中撰寫登錄邏輯:
// src/modules/user/model.ts
export class Model extends BaseModel<ModuleState> {
@reducer
public setUser(curUser: User) {
this.state.curUser = curUser;
}
@effect()
public async login(username: string, password: string) {
const { data } = await api.login(username, password);
await this.dispatch(this.actions.setUser(data));
this.getRouter().relaunch({url: HomeUrl});
}
}
article/model.ts中通過reducer監聽setUserAction:
// src/modules/article/model.ts
export class Model extends BaseModel<ModuleState> {
@reducer
public ['user.setUser'](curUser: User) {
//根據當前用戶是否登錄來決定是否可編輯
this.state.editable = curUser.hasLogin;
}
}
my/model.ts中通過effect監聽setUserAction:
// src/modules/my/model.ts
export class Model extends BaseModel<ModuleState> {
@reducer
public updateNotices(notices: Notices[]) {
this.state.notices = notices;
}
@effect()
public async ['user.setUser'](curUser: User) {
if(curUser.hasLogin){
const notices = await this.api.getNotices();
this.dispatch(this.actions.updateNotices(notices));
}
}
}
user/views/Login.tsx中派發loginAction:
// src/modules/user/views/Login.tsx
export default ({dispatch}) => {
const login = () => {
dispatch(userActions.login('admin', '123456'));
};
return (
<div>
<button onClick={login} >登錄</button>
</div>
);
}
統一化
資料模式有2大基本陣營:ImmutableData 和 MutableData,Redux是ImmutableData陣營的代表;Vue為MutableData的代表,
Elux可以同時兼容這2種資料模式,它們的唯一區別在reducer中:
- ImmutableData:要求回傳一個新資料,不可以修改原資料,
- MutableData:可以直接修改原資料,
class Model{
@reducer
setUser(curUser) {
//vue中可以直接修改state:
this.state.curUser = curUser;
//react中必需回傳一個新state
//return {...this.state, curUser};
}
}
當然,在MutableData模式下,回傳一個新資料也是可以的,這為跨React和Vue專案共享Model提供了解決方案,
await dispatch
actionHander中如果有異步操作,將回傳一個promise,可以await其執行,例如:
// src/modules/user/views/Login.tsx
const onSubmit = (values: HFormData) => {
const result = dispatch(userActions.login(values));
result.catch(({message}) => {
//如果出錯(密碼錯誤),在form中展示出錯資訊
form.setFields([{name: 'password', errors: [message]}]);
});
};
跟蹤effect執行情況
通常effect中包含異步操作,對于異步操作我們通常都需要顯示Loading,Elux中可以很方便的跟蹤它的執行情況,只需要在裝飾器effect()中傳入Loading狀態Key名即可,
- @effect('this.loginLoading'):表示將執行情況注入
this.state.loginLoading中 - @effect() 不傳引數等于@effect('stage.globalLoading'):表示將執行情況注入
stage.state.globalLoading中 - @effect(null):引數為null表示不跟蹤執行情況
// src/modules/user/model.ts
export class Model extends BaseModel<ModuleState> {
@effect('this.loginLoading') //將該方法的執行情況注入this.state.loginLoading中
public async login(username: string, password: string) {
const { data } = await api.login(username, password);
await this.dispatch(this.actions.setUser(data));
this.getRouter().relaunch({url: HomeUrl});
}
}
在View中使用loginLoading狀態
// src/modules/user/views/Login.tsx
export default ({dispatch, loginLoading}) => {
return (
<div>
<button onClick={login} disable={loginLoading==='Start'} >登錄</button>
</div>
);
}
自動合并和維護Loading佇列
不僅可以很方便的跟蹤和注入loading狀態,框架還自動維護loading佇列,比如相同Key名的多筆loading狀態將自動合并成佇列管理(佇列中的任務全部完成即改變loading狀態),
自動區分淺度Loading和深度Loading
export type LoadingState = 'Start' | 'Stop' | 'Depth';
比如不超過1秒的loading為淺度Loading,否則為深度Loading,這樣區分的好處是:對于淺度Loading只需要防止用戶重復點擊,視覺上用戶不用感知,否則會出現一閃而過的Loading界面,反而會影響用戶體驗,
const Component: FC<Props> = ({loadingState}) => {
return (
<div className="global-loading">
{loadingState === 'Depth' && <div className="loading-icon" />}
</div>
);
};
方便的錯誤處理
effect執行中出現任何失敗或者錯誤,都將自動派發一個stage._error的內置action,可以監聽它來集中處理錯誤:
// src/modules/stage/model.ts
export class Model extends BaseModel<ModuleState> {
@effect(null)
protected async ['this._error'](error: CustomError) {
if (error.code === CommonErrorCode.unauthorized) {
this.getRouter().push({url: '/login'}, 'window');
}else{
alert(error.message);
}
throw error;
}
}
支持泛監聽
可以使用一個Hander監聽多個Action:
- 使用
,符號分隔多個actionType - 使用
*符號作為moduleName的通配符 - 使用
this可以指代本模塊名
class Model extends BaseModel
@effect()
//同時監聽2個模塊的'_initState'
async ['moduleA._initState, moduleA._initState'](){
console.log('moduleA/moduleB inited');
}
@effect()
//同時監聽所有模塊的'_initState'
async ['*._initState'](){
console.log('all inited');
}
}
還可以路由守衛
Elux中的路由發生跳轉時會自動派發幾個內置的action:
stage._testRouteChange:是否允許本次跳轉,你可以監聽它,阻止路由跳轉:export class Model extends BaseModel<ModuleState> { private checkNeedsLogin(pathname: string): boolean { return pathname.startsWith('/admin/') } @effect(null) protected async ['this._testRouteChange']({url, pathname}) { if (!this.state.curUser.hasLogin && this.checkNeedsLogin(pathname)) { throw new CustomError(CommonErrorCode.unauthorized, '請登錄!'); } } }stage._beforeRouteChange:路由即將跳轉,你可以監聽它,執行某些邏輯...stage._afterRouteChange:路由跳轉完成,你可以監聽它,執行某些邏輯...
多實體歷史快照
- 路由push時你可以將當前Store實體凍結起來,并保存在歷史堆疊中,
- 路由back時將自動激活之前被凍結的Store實體,快速恢復歷史狀態,
自動清理無用狀態
傳統全域Store有個很大的弊端,就是Store中的狀態會不斷累積,缺乏自動釋放機制,比如當前路由從用戶串列跳轉到了文章串列,如果不主動操作,Store中的userList可能一直存在,
Elux改進了這個痛點,每次路由發生變化時都將創建一個空的Store,然后挑選出有用的狀態重新掛載,這也相當于一種自動垃圾回識訓制,
應用
Elux框架奉行輕UI、重Model的領域驅動理念,推薦將業務邏輯與UI邏輯剝離,進行抽象的業務邏輯建模,從而讓業務Model可以跨框架、跨平臺、跨工程復用,
而其內置的狀態管理框架,有效的支撐了這一設計理念,更多資訊參見:
- 微模塊-前端業務模塊化探索,拆解巨石應用的又一利器
- 官網:https://eluxjs.com
最后
好了,感謝小伙伴們耐心看到這里,正如標題所言,如果還是覺得不好,現在可以來打我了??,坐標:廣西東興,o友情提醒:泡面不要帶少了哦...
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/508836.html
標籤:其他
上一篇:前端面試題JavaScript篇——2022-09-15
下一篇:前端像素鳥小游戲
