使用react-hook 重写 react-redux

栏目: 服务器 · 发布时间: 5年前

内容简介:react-redux主要提供的功能是将redux和react链接起来。 使用提供的connect方法可以使得任意一个react组件获取到全局的store上的状态。 实现方法是将store存放于由provider提供的context上,在调用connect时, 就可将组件的props替换, 让其可以访问到定制化的数据或者方法。本文将尝试使用最近很火爆的react-hook来替代react-redux的基础功能。我们先将理想的特征列举出来,完成这些特性才算是替代了react-redux:

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

目标

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

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

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

useRudecer

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

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保持的数据都是独立的。比如下面这个例子:

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

两个Count组件内部的数据是独立的,无法互相影响,状态管理也就无从说起。 究其原因,useReducer内部也是用useState实现的

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。

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.

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

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,来更改state

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


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

游戏编程入门

游戏编程入门

莫里森 / 人民邮电出版社 / 2005-9 / 49.00元

本书介绍如何设计和构建自己的计算机游戏。书中从零开始,引导读者开发一个“即插即用”的游戏引擎,并基于该引擎,循序渐进地开发7个完整的游戏。全书分为8个部分,共24章,内容包括游戏编程基础知识、如何与玩家交互、使用子画面动画、使用声音和音乐、高级动画、游戏人工智能、增添游戏的趣味性和附加练习。此外,在随书光盘中提供有附录,包括C++语言和windows编程的入门指导、游戏开发工具以及游戏图形创建的介......一起来看看 《游戏编程入门》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

html转js在线工具
html转js在线工具

html转js在线工具