谈谈useContext + useReduce,hox与mobx-state-tree三种状态管理方案

1,430 阅读4分钟

1. 背景

任何一个工具,技术的出现都是为了解决一个或多个问题。 同样,状态管理是为了解决(React为例,React没有全局状态):

  • 数据共享
  • 组件全局通信 当你在这两个问题上遇到了解决不了的问题后,可引入状态管理

2. useContext + useReduce

React hooks提供了两个hooks API,分别为:

  1. useContext
  2. useReduce useContext可在顶层组件注入共享数据,其任意子组件均能使用,解决了数据共享问题。

useReduce提供返回的[state, dispatch]dispatch可修改数据状态,组件A修改状态,组件B可拿到修改后的状态,解决组件全局通信问题。

使用方法

思路(Store组件中):

  1. const [state, dispatch] = useReducer(reducer, initialState);利用useReducer来返回一个state(需要共享的数据)和dispatch(修改数据的方法)。
  2. 通过const Context = createContext();来创建一个Context对象。
  3. 使用Context.Provider包裹需要共享数据的顶级组件,并注入数据statedispatch方法。
  4. 通过useContext(Context)来拿到共享数据,通过export useContext(Context)让其他子组件可以通过引入的方式拿到该共享数据。

代码结构为:

<Store>
    <Parent store={[state, dispatch]}> // store是注入的数据
        <ChildA>
        <ChildB>
    </Parent>
</Store>

完整代码如下所示:

1. Store.jsx

import React, { createContext, useReducer, useContext } from 'react';

const initialState = {count: 10};

const reducer = (state, action) => {
    switch (action.type) {
        case 'increment':
            return {count: state.count + 1};
        case 'decrement':
            return {count: state.count - 1};
        default:
            throw new Error();
    }
}

// step 2 创建一个Context
const Context = createContext();
// step 4 获取数据并在最后export
const useStore = () => useContext(Context);


const StoreProvider = props => {
    // step 1,useReducer返回state, dispatch
    const [state, dispatch] = useReducer(reducer, initialState);
    return (
        // step 3 注入数据
        <Context.Provider value={[state, dispatch]}>
            {props.children}
        </Context.Provider>
    );
}

export { useStore, StoreProvider };

2. Parent.jsx

import React from 'react';
import { StoreProvider } from './store';
import ChildA from './ChildA';
import ChildB from './ChildB';

const Parent = () => {
    return (
    <div>
       <p>这是react hooks自带的createContext, useReducer, useContext实现的</p>
       <StoreProvider>
            <ChildA />
            <ChildB />
       </StoreProvider>
    </div>
    );
}

export default Parent;

3. ChildA.jsx

import React from 'react';
import { useStore } from './store.js';

const ChildA = () => {
    const [, dispatch] = useStore();
    
    return (
        <>
            <button onClick={() => dispatch({type: 'decrement'})}>-</button>
            <button onClick={() => dispatch({type: 'increment'})}>+</button>
        </>
    );
}

export default ChildA;

4. ChildB.jsx

import React from 'react';
import { useStore } from './store';

const ChildB = () => {
    const [state] = useStore();
    return <p>{state.count}</p>;
}

export default ChildB;

3. hox

hox是由蚂蚁金服体验技术开发,号称是下一代React状态管理器,完全拥抱React Hooks,具有以下特点

  • 仅一个API,上手简单
  • 完美支持TypeScript
  • 使用 custom Hooks 来定义 model,完美契合 React Hooks

使用方式

思路:

  1. 定义Model:使用createModel()包裹任意一个custom hook(这里为useCounter
  2. 使用Model: 通过useCounterModel()取到数据成功;

1. Store.js(定义store)

import { useState } from "react";
import { createModel } from "hox";

const useCounter = initialValue => {
    const [count, setCount] = useState(initialValue ?? 10);
    const decrement = () => setCount(count - 1);
    const increment = () => setCount(count + 1);
    return {
        count,
        decrement,
        increment
    };
}
// 任意一个custom Hook,用createModel包装后,就变成了持久化且全局共享的数据。包装后依旧是个custom Hook
// createModel(useCounter) 相当于const useCounterModel = createModel(useCounter);
export default createModel(useCounter);
// 带初始值的
// export default createModel(useCounter, 20);

2. Counter.jsx(使用store)

import React from 'react';
// useCounterModel 是一个真正的Hook,会订阅数据的更新。当点击 "+" 按钮时,会触发model的更新,并且最终通知所有使用useCounterModel的组件或Hook。
import useCounterModel from "./model";

const Counter = () => {
  // createModel返回值是个Hook,可以按React Hooks的用法正常使用它
  const counter = useCounterModel();
  return (
    <div>
      <p>{counter.count}</p>
      <button onClick={counter.increment}>+</button>
      <button onClick={counter.decrement}>-</button>
    </div>
  );
};
export default Counter;

3. mobx-state-tree

mobx-state-tree是基于mobx开发的一个状态管理工具,核心思想是通过types生成Model,Model可定义一个对象结构,一个types可嵌套多个Model,从而生成一个动态树。可理解为该动态树对应的就是一个对象的数据结构

使用方式

思路:

  1. 通过types生成一个counterStoreModel
  2. 通过counterStore.create()生成一个实例
  3. observer修饰需要使用store的组件,通过props拿到数据

完整的代码如下:

1. store.js(定义store)

import React, { createContext, useContext } from 'react';
import {types} from 'mobx-state-tree';
console.log('types:', types);
const CounterStore = types
    .model('counterStore', {
        count: types.number
    })
    .actions(self => ({
        increment() {
            self.count++;
        },
        decrement() {
            self.count--;
        }
    }));

export default CounterStore.create({count: 10});

2. Counter.js(使用store)

import React from 'react';
import {observer} from 'mobx-react';

// 要想获取更新后的值 必须有observer
const Counter = observer(props => {
    // 组件传值使用
    const {store} = props;

    return (
        <div>
            <p>{store.count}</p>
            <button onClick={store.increment}>+</button>
            <button onClick={store.decrement}>-</button>
        </div>
    );
});
export default Counter;

5. 如何确定选择哪一个方案

首先,三种状态管理方案基本都能解决文章开头提出的两个问题,故可按照下面顺序选择:

  1. 简单项目不需要状态管理方案,使用状态提升即可
  2. 使用hox,上手简单,契合hooks
  3. 使用useRedux + usecontext, 官方提供,上手难度高于hox,但也算简单
  4. 使用mobx,上手难度较高

参考资源: