react-redux
去做状态管理,现在的 react-redux
已经支持 hooks 了,今天我们就来实现一款简易版本的 react-redux
1. 我们需要实现什么
- Provider 组件,用于存储 store,挂载带有 context 的组件
- useDispatch 用来获取 dispatch
- useSelect 用来在组件中获取 store 中的数据
2. 确定我们的用法
- 最外层是我们的 Provider 组件,即
const store = createStore(reducer); <Provider store={store}> <App/> </Provider>
- 在组件中获取 store中的数据展示,获取 dispach,改变store中的值,即
const ChildA = () => { // selector 获取 store 中指定的数据。如:state => state.count // equalityFn 通过比较前后的 state 来判断此次是否需要 re-render const { count } = useSelct(selector, equalityFn); const disptach = useDispatch(); return ( <div> <p onClick={() => dispatch({ type: 'ADD_COUNT'})}>child B:{count}</p> </div> ); };
3.实现 Provider 组件
这个组件其实很简单,接受 store 作为props,然后将 store传给 Context.Provider 即可
Context 官方文档
const StoreContext = React.createContext();
const Provider = ({ store, children }) => {
return <StoreContext.Provider value={store}>{children}</StoreContext.Provider>;
};
4. 实现 useDispatch
将 store 中的 dispatch 方法返回即可
useContext 官方文档
const useDispatch = () => {
const store = useContext(StoreContext);
const dispatch = store.dispatch;
return dispatch;
};
5. 实现 useSelect
- 我们先不管获取指定的state,跟比较前后state这两个功能,先就简单的实现以下,从 store 中获取state,那么实现就很简单
const useSelect = () => {
const store = useContext(StoreContext);
const state = store.getState();
return state;
};
- 我们加入
selector
方法,获取指定的 state,这时候,我们只需简单的改动以下代码即可
const useSelect = (selector= data => data) => {
const store = useContext(StoreContext);
try {
const state = selector(store.getState());
return state;
} catch(e) {
throw new Error(`useSelect.selector get data Error. Err: ${e} `)
}
};
- 做完之后,我们发现,如果我的store里面的值怎么改变都不会重新触发 re-render, 应为对于组件而言,他并没有状态改动,store一直是没有变化的。那么我们只需监听以下store里面的值的变化,然后触发更新
const useSelect = (selector= data => data) => {
const [, forceRender] = useReducer((s) => s + 1, 0);
const store = useContext(StoreContext);
useEffect(() => {
const unsubscribe = store.subscribe(() => {
forceRender();
});
return unsubscribe;
}, [store])
try {
const state = selector(store.getState());
return state;
} catch(e) {
throw new Error(`useSelect.selector get data Error. Err: ${e} `)
}
};
- 这样的问题来了,即使我们组件引用的state没有发生改变,也会触发更新,这样对性能而言是可以优化的,最后我们引入我们的 equalityFn
const useSelect = (selector = (data) => data, equalityFn) => {
const [, forceRender] = useReducer((s) => s + 1, 0);
const store = useContext(StoreContext);
let state;
try {
state = selector(store.getState());
} catch(e) {
throw new Error(`useSelect.selector get data Error. Err: ${e} `)
}
const lastDataCache = useRef({ state, selector, equalityFn });
lastDataCache.current.selector = selector;
lastDataCache.current.equalityFn = equalityFn;
const checkForUpdate = useCallback(() => {
try {
const newState = lastDataCache.current.selector(store.getState());
const result = lastDataCache.current.equalityFn(lastDataCache.current.state, newState);
// 需要更新
if (!result) {
forceRender();
lastDataCache.current.state = newState;
}
} catch (e) {
console.warn(`useSelect.useSelect Error. Err:${e}`)
forceRender();
}
}, [store]);
useEffect(() => {
const unsubscribe = store.subscribe(checkForUpdate);
return unsubscribe;
}, [store, checkForUpdate]);
return state;