本文寫于 2020 年 1 月 6 日
React 社區(qū)最火的全局狀態(tài)管理庫必定是 Redux,但是 Redux 本身就是為了大型管理數(shù)據(jù)而妥協(xié)設(shè)計的——這就會讓一些小一點的應(yīng)用一旦用上 Redux 就變得復(fù)雜無比。
后來又有了 Mobx,它對于小型應(yīng)用的狀態(tài)管理確實比 Redux 簡單不少。可是不得不說 Mobx+React 簡直就是一個繁瑣版本的 Vue。所以我也不太喜歡,不如直接用 Vue3。
總而言之,不管是 react-redux 還是 mobx,他們使用的時候都非常復(fù)雜,甚至需要你去組件函數(shù)或是組件類上修修改改,從審美角度上來說就令人不太喜歡。
直到后來某一天用了 Angular,我就開始對 SOA 產(chǎn)生好感,ng 的 Service 的寫法與依賴注入控制反轉(zhuǎn)著實驚艷到了我。
Service 是 Angular 的邏輯復(fù)用方法,并且解決了共享狀態(tài)的問題,那 React 的自定義 Hook 可以達(dá)到類似的效果嘛?
可以,并且會比 Angular 更簡潔!??!
我們先來想一下,Service 到底是什么?
例如下面這個負(fù)責(zé) Todo List 記錄的 Service:
class TodoRecordService {
private todoList: Record[] = [];
get getTodoList() {
return this.todoList;
}
public addRecord(newRecord: Record) {
this.todoList.push(newRecord);
}
public deleteRecord(id: string) {
this.todoList = this.todoList.filter((record) => record.id !== id);
}
public getRecord(id: string) {
const targetIndex = this.todoList.findIndex((record) => record.id === id);
return { index: targetIndex, ele: this.todoList[targetIndex] };
}
}
那我們用 React 如何實現(xiàn)一個狀態(tài)共享的單例呢?
使用 Context
與 useContext
即可。
接下來我們做一個最簡單的計數(shù)器吧:一個負(fù)責(zé)計數(shù)的 button,一個負(fù)責(zé)顯示當(dāng)前數(shù)值的 panel。
const App: React.FC = () => {
return (
<div>
<Button />
<Panel />
</div>
);
};
然后我們來定義我們的 Service:
interface State {
count: number;
handleAdd: () => void;
}
export const CountService = createContext<State>(null);
我們選擇讓一個 Context 成為一個 Service,這是因為我們可以利用 Context 的特性來進(jìn)行狀態(tài)共享,達(dá)到單例的效果。
但是光這樣還不行,我們想讓 count
擁有響應(yīng)性,就必須使用 useState
(或者其他 hook)來創(chuàng)建。
因此需要一個自定義 Hook,并且在 Context.Provider
中傳入 Provider
的 value
值:
interface State {
count: number;
handleAdd: () => void;
}
export const CountService = createContext<State>(null);
export const useRootCountService = () => {
const [count, setCount] = useState<number>(0);
const handleAdd = useCallback(() => {
setCount((n) => n + 1);
}, []);
return {
count,
handleAdd,
};
};
那么在組建中,我們?nèi)绾问褂?Service 呢?
非常簡單:
const App: React.FC = () => {
const countService = useContext(CountService);
return <div>{countService.count}</div>;
};
所以計數(shù)器的完整代碼應(yīng)該這么寫:
import { CountService, useRootCountService } from './service/count.service';
const App: React.FC = () => {
return (
<CountService.Provider value={useRooCountService()}>
<div>
<Button />
<Panel />
</div>
</CountService.Provider>
);
};
// Button.tsx
import { CountService } from '../services/global.service';
const Button: React.FC = () => {
// 注意,此處是故意寫復(fù)雜了,是為了凸顯跨組件狀態(tài)管理的特性
const countService = useContext(CountService);
return <button onClick={() => countService.handleAdd()}>+</button>;
};
// Panel.tsx
import { CountService } from '../services/global.service';
const Panel: React.FC = () => {
const countService = useContext(CountService);
return <h2>{countService.count}</h2>;
};
對于小組件而言,剛剛的寫法已經(jīng)足夠了。
但是要知道,Service 是高度集中的某個模塊的狀態(tài)與方法,我們不能保證 Service 的方法可以直接用到組件的邏輯中去。
所以需要我們在組件內(nèi)部對于邏輯進(jìn)行二次拼裝。
但是把邏輯直接寫到組件里面是一件非常惡劣的事情?。?!
幸好,React 有了 hooks 讓我們?nèi)コ殡x邏輯代碼。
const useLogic1 = () => {
// 在 hook 中獲取服務(wù)
const xxxService = useContext(XxxService);
// ...
const foo = useCallback(() => {
// ...
xxxService.xxxx();
// ...
}, []);
return {
// ...
foo,
};
};
const SomeComponent: React.FC = () => {
// 復(fù)用邏輯
const { a, b, foo } = useLogic1(someParams);
const { c, bar } = useLogic2();
return (
<div>
<button onClick={() => bar()}>Some Operation</button>
</div>
);
};
這種形式的組件,便是我們的目標(biāo)。
(完)
聯(lián)系客服