hooks 实现简易版 react-redux

2,373 阅读2分钟

在我的日常开发过程中,经常会用到 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;

代码地址