使用 react 的 hooks 進行全局的狀態管理
React 最新正式版已經支持了 Hooks API,先快速過一下新的 API 和大概的用法。
// useState,簡單粗暴,setState可以直接修改整個state
const [state,setState] = useState(value);
// useEffect,支持生命周期
useEffect(()=>{
// sub
return ()=>{
// unsub
}
},[]);
// useContext,和 React.createConext() 配合使用。
// 父組件使用 Context.Provider 生產數據,子組件使用 useContext() 獲取數據。
const state = useContext(myContext);
// useReducer,具體用法和redux類似,使用dispatch(action)修改數據。
// reducer中處理數據並返回新的state
const [state, dispatch] = useReducer(reducer, initialState);
// useCallback,返回一個memoized函數,第二個參數類似useEffect,只有參數變化時才會更改。
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
// useMemo,返回一個memoized值,只有第二個參數發生變化時才會重新計算。類似 useCallback。
// useCallback(fn,inputs) 等效 useMemo(() => fn,inputs)。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b])
// useRef,返回一個可變的ref對象
const refContainer = useRef(initialValue);
// useImperativeMethods,詳情自行查閱文檔
// useMutationEffect,類似useEffect,詳情自行查閱文檔
// useLayoutEffect,類似useEffect,詳情自行查閱文檔
一開始以為使用 useState 分離數據層 和 UI 層,就可以達到數據共享了,后來發現想的太簡單了。分離只是邏輯共享,數據都是獨立的。
后來發現有個 useReducer 似乎和 redux 很像,然而本質上,還是 useState 實現的。
再繼續探索發現,基於useContext,同時配合useReducer一起使用。
但是,這個方案的缺陷是,當數據太大,組件太多,會直接導致渲染性能下降。
每一次state的變化,都會從頂層組件傳遞下去,性能影響比較大。
當然也有一些優化手段,比如使用memo()或者useMemo(),又或者拆分更細粒度的context,對應不同的數據模塊,再包裝成不同的ContextProvider,只是這樣略顯繁瑣了。
后面,終於找到一位大神的作品,經過簡單的修改后得出一個可以使用的辦法:
import { useState,useEffect } from 'react';
const isFunction = fn => typeof fn === 'function';
const isObject = o => typeof o === 'object';
const isPromise = fn => {
if (fn instanceof Promise) return true;
return isObject(fn) && isFunction(fn.then);
};
// Model 類
class Model {
constructor({initialState,actions}){
this.state = initialState;
this.actions = {};
this.queue = [];
Object.keys(actions).forEach((name)=>{
this.actions[name] = (arg)=>{
const res = actions[name].call(this,this.state,arg);
if(isPromise(res)){
Promise.resolve(res).then((ret)=>{
this.state = ret;
this.onDataChange();
});
}else{
this.state = res;
this.onDataChange();
}
}
});
}
useStore(){
const [, setState] = useState();
// 使用useEffect實現發布訂閱
useEffect(() => {
const index = this.queue.length;
this.queue.push(setState); // 訂閱
return () => { // 組件銷毀時取消
this.queue.splice(index, 1);
};
},[]);
return [this.state, this.actions];
}
onDataChange(){
const queues = [].concat(this.queue);
queues.forEach((setState)=>{
setState(this.state); // 通知所有的組件數據變化
});
}
}
// models/user.js
const sleep = async t => new Promise(resolve => setTimeout(resolve, t));
const initialState = {};
const actions = {
async setUserInfo(){
await sleep(2000);
return Promise.resolve({name:"mannymu",age:18});
},
setAge(state,age){
return Object.assign({},{...state,age});
},
setName(state,name){
return Object.assign({},{...state,name});
},
loginOut(){
return null;
}
}
const user = new Model({
initialState,
actions
});
// 組件
import {useStore} from '../models/user';
const Person = ()=>{
const [state,actions] = useStore();
return (
<div>
<span> My name is {state.name}.</span>
<button onClick={()=> actions.setName('han meimei.')}>btn1</button>
</div>
)
};
這樣就可以使用簡單的代碼,管理復雜的狀態了。同時支持異步。
當然這里是直接采用的返回值覆蓋 state ,如果異常情況(ajax或者其他報錯)處理的不夠好,可能會出現奇怪的問題,報錯后 return 的值如果不確定,就會出現奇怪效果。
可以參考 redux 的辦法,再擴展一個reducer 來專門修改數據,action 只負責觸發修改。
參考文檔:https://blog.csdn.net/tzllxya/article/details/92798097
參考github: https://github.com/yisbug/iostore 有興趣的可以看看,這份代碼,質量很高,值得一讀
通過查看那位大佬在 github 上面的完整版狀態管理插件源碼。其基於上面的代碼基本原理。做了 Proxy 的封裝,就可以像 vue 一樣,直接修改 this.xxx 來更新視圖層。
最后,附上我的本地文件夾分類:

其中 store.js 里面,是上面的 Model 類。
