使用react-hook 替代 react-redux

前文

react-redux主要提供的功能是将redux和react连接起来。 使用提供的connect方法可使得任意一个react组件获取到全局的store。 实现方法是将store存放于由provider提供的context上,在调用connect时, 就可将组件的props替换, 让其能够访问到定制化的数据或者方法。react

目标

本文将尝试使用最近很火爆的react-hook来替代react-redux的基础功能。git

咱们先将理想的特征列举出来,完成这些特性才算是替代了react-redux:github

  • 全局维护一个store。
  • 任何组件均可以获取到store,最好props能够定制(mapStatetoProps)。
  • 提供能够派发action的能力(mapDispatchtoProps)。

useRudecer

先看一下内置useRudecer的官方实例能给咱们带来一些什么启示:redux

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'reset':
      return initialState;
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      // A reducer must always return a valid state.
      // Alternatively you can throw an error if an invalid action is dispatched.
      return state;
  }
}

function Counter({initialCount}) {
  const [state, dispatch] = useReducer(reducer, {count: initialCount});
  return (
    <div> Count: {state.count} <button onClick={() => dispatch({type: 'reset'})}> Reset </button> <button onClick={() => dispatch({type: 'increment'})}>+</button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> <div/> ); } 复制代码

乍一看好像react利用hook已经可使用redux的机制了, 状态由派发的action改变,单向数据流。可是hook不会让状态共享,也就是每次useReducer保持的数据都是独立的。好比下面这个例子:app

function CountWrapper() {
    return (
        <section>
            <Counter initialCount={1}/>
            <Counter initialCount={1}/>
        </setion>
        )
}
复制代码

两个Count组件内部的数据是独立的,没法互相影响,状态管理也就无从提及。 究其缘由,useReducer内部也是用useState实现的ide

function useReducer(reducer, initialState) {
  const [state, setState] = useState(initialState);

  function dispatch(action) {
    const nextState = reducer(state, action);
    setState(nextState);
  }

  return [state, dispatch];
}
复制代码

StoreProvider

useReducer看来并不能帮上忙。解决全局状态的问题能够参照react-redux的作法,提供一个Provider,使用context的方式来作。 这里可使用useContext,这个内置的hook。this

Accepts a context object (the value returned from React.createContext) and returns the current context value, as given by the nearest context provider for the given context. When the provider updates, this Hook will trigger a rerender with the latest context value.spa

它接受一个由React.createContext返回的上下文对象, 当provider更新时,本文中这里理解为传入的store更新时,useContext就能够返回最新的值。那么咱们就有了下面的代码rest

import {createContext, useContext} from 'react';

const context = createContext(null);
export const StoreProvider = context.provider;

const store = useContext(context);
// do something about store.

复制代码

useDispatch

到这里咱们提供了一个根组件来接受store。当store有更新时,咱们也能够利用useContext也能够拿到最新的值。 这个时候暴露出一个hook来返回store上的dispatch便可派发action,来更改statecode

export function useDispatch() {
  const store = useContext(Context);
  return store.dispatch;
}
复制代码

useStoreState

接下来着眼于组件拿到store上数据的问题。这个其实也很简单,咱们都把store拿到了,编写一个自定义的hook调用store.getStore()便可拿到全局的状态,

export function useStoreState(mapState){
    const store = useContext(context);
    return mapState(store.getStore());
}
复制代码

这里虽然是把状态拿到了,但忽略了一个很是重要的问题, 当store上的数据变化时,如何通知组件再次获取新的数据。当store变化事后,并无和视图关联起来。另外一个问题是没有关注mapState变化的状况。 针对第一个问题,咱们能够利用useEffect这个内置hook,在组件mount时完成在store上的订阅,并在unmont的时候取消订阅。 mapState的变动可使用useState来监听, 每次有变动时就执行向对应的setter方法。代码以下

export function useStoreState(mapState) {
    const store = useContext(context);

    const mapStateFn = () => mapState(store.getState());

    const [mappedState, setMappedState] = useState(() => mapStateFn());

    // If the store or mapState change, rerun mapState
    const [prevStore, setPrevStore] = useState(store);
    const [prevMapState, setPrevMapState] = useState(() => mapState);
    if (prevStore !== store || prevMapState !== mapState) {
        setPrevStore(store);
        setPrevMapState(() => mapState);
        setMappedState(mapStateFn());
    }

    const lastRenderedMappedState = useRef();
    // Set the last mapped state after rendering.
    useEffect(() => {
        lastRenderedMappedState.current = mappedState;
    });

    useEffect(
        () => {
            // Run the mapState callback and if the result has changed, make the
            // component re-render with the new state.
            const checkForUpdates = () => {
                const newMappedState = mapStateFn();
                if (!shallowEqual(newMappedState, lastRenderedMappedState.current)) {
                    setMappedState(newMappedState);
                }
            };
                        
            // Pull data from the store on first render.
            checkForUpdates();

            // Subscribe to the store to be notified of subsequent changes.
            const unsubscribe = store.subscribe(checkForUpdates);

            // The return value of useEffect will be called when unmounting, so
            // we use it to unsubscribe from the store.
            return unsubscribe;
        },
        [store, mapState],
    );
    return mappedState
}

复制代码

如上就完成了hook对react-redux的功能重写,从代码量来讲是简洁量很多,而且实现方式也更贴合react将来的发展方向。 可见大几率上react-redux会被hook的方式逐渐替代。本文是对redux-react-hook实现的原理讲解,想要在线尝试本文所诉内容点击这个codesandbox

相关文章
相关标签/搜索