【React.js】Store 的平替方案

Store 为什么需要被平替?

首先,Store (e.g Redux、Mobx)是为了管理大型项目的全局状态的,Store 为所有的全局状态提供了可预测,可操作,流程化的 API 方法。

但是,是不是所有的全局状态都需要存放在 Store 中呢?

答案是否定的。对于一些简单的全局状态或者是只是短暂使用到的全局状态,我们没有必要经过诸如派发器注解装饰器这样的一些操作较为复杂的流程。

这个时候,Store 的平替方案就派上作用了,它的目的也显而易见:

  1. 我需要一些全局状态 & 能够修改全局状态的方法
  2. 我不想要太复杂的操作。

Store 的常见平替方案:

1. 使用 context API 来实现自定义 hook

使用 context-API + 内置的 react-hook 就能很简单地解决这样的问题,举个登录的例子:

  1. 先注册一个 Context
js 复制代码
import { createContext } from 'react';

export const LoginContext = createContext({
  username: '',
  password: '',
  token: '',

  dispatch: (type, payload) => {}
});
  1. 写一个自定义 hook useLogin (这里使用的是 useState 封装,也可以使用 useReducer)
js 复制代码
import { useState, useEffect } from 'react';

export function useLogin() {
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const [token, setToken] = useState('');
  
  function dispatch(type, payload) {
    switch (type) {
      case 'setUsername':
        // do something
        break;
      case 'setPassword':
        // do something
        break;
      case 'setToken':
        // do something
        break;
      default:
        break;
    }
  }

  useEffect(() => {
    setToken(window.localStorage.getItem('token'));
  }, []);

  useEffect(() => {
    // 校验登录 ...
  }, []);
  
  return {
    username,
    password,

    dispatch
  };
}
  1. 在 App 组件中,使用 useLogin自定义 hook
jsx 复制代码
import React from 'react';
import { LoginContext } from './contexts';

function App() {
  const loginContextValue = useLogin();

  return (
    <LoginContext.Provider value={loginContextValue}>
      {/* 其他内容 */}
    </LoginContext.Provider>
  );
}

export default App;
  1. 在子组件中使用:
jsx 复制代码
import { useContext } from 'react';
import { LoginContext } from '@/contexts';

function Child() {
  const { username, password, dispatch } = useContext(LoginContext);

  return (
    <div>{/*渲染内容*/}</div>
  );
}

2. 使用自定义 hook : 封装一个 useCtxModel (推荐)

直接使用 react 内置的 hook 有时候还是不能满足我们对于集成性数据操作的期望。

这个时候,封装一个自定义 hook 是比较合适的。下面的演示是为了阐述如何使用一个自定义 hook 来对 Model 进行操作:

2.1. 使用步骤:

  1. 声明一个 CtxModel:
js 复制代码
import { CtxModel } from 'my-hooks';

export const ctxModel = new CtxModel({
  state: {
    username: '',
    password: '',
  },
  actions: {
    setUsername(state, payload) {
      state.username = payload;
    },
    setPassword(state, payload) {
      state.password = payload;
    },
    replaceState(state, newState) {
      Object.assign(state, newState);
    }
  },
});
  1. 在父组件中使用 useCtxModel获取 ctxModel中的 Provider,并把它们包裹起来:
jsx 复制代码
/* 在父组件中挂载 */
import React from 'react';

import { CtxModel, useCtxModel } from 'my-hooks';
import { ctxModel } from '@/models';

function App() {
  const {
    username,
    password,

    setUsername,
    setPassword,

    CtxModelProvider
  } = useCtxModel(ctxModel);

  return (
    <CtxModelProvider>
      {/* 需要渲染的内容 */}
    </CtxModelProvider>
  );
}

export default App;
  1. 在子组件中获取 ctxModel 中真实存储的值或者 <Consumer>, 并渲染到视图中:
jsx 复制代码
/* 在子组件中使用 */
import React, { useContext } from 'react';

import { ctxModel } from '@/models';
import { useCtxModelContext } from 'my-hooks';

function Child1() {
  const ctxModelValue = useContext(ctxModel.getContext());
  const {
    username,
    password,

    setUsername,
    setPassword,
  } = ctxModelValue;

  return (
    <div>{JSON.stringify(ctxModelValue)}</div>
  );
}

function Child2() {
  const CtxModelConsumer = ctxModel.getModelConsumer();

  return (
    <CtxModelConsumer>
      {ctxModelValue => {
        return (
          <div>{JSON.stringify(ctxModelValue)}</div>
        );
      }}
    </CtxModelConsumer>
  );
}

function Child3() {
  const ctxModelValue = useCtxModelContext(ctxModel);
  /*
    const {
      username,
      password,
  
      setUsername,
      setPassword,
    } = ctxModelValue;
  */

  return (
    <div>{JSON.stringify(ctxModelValue)}</div>
  );
}

export {
  Child1,
  Child2,
  Child3
};

2.2. 代码实现

shell 复制代码
my-hooks
    |- hooks
        |- useCtxModel.js
        |- useCtxModelContext.js
        |- index.js
    |- models
        |- CtxModel.js
        |- index.js
    |- index.js
  1. 从 my-hooks 中导入这些组件
js 复制代码
/* my-hooks */
export {
  // ... sub hooks
  useCtxModel,
  useCtxModelContext
} from './hooks';

export {
  // ... sub models
  CtxModel
} from './models';
  1. 封装 useCtxModel
js 复制代码
import { useState, useMemo } from 'react';
import _ from 'lodash';

function useCtxModel(model) {
  const initialState = model.getState();
  const actions = model.getActions();

  const [state, setState] = useState(initialState);

  const mapActionsMethods = useMemo(() => {
    const methods = {};
    for (let actionType in actions) {
      if (actions.hasOwnProperty(actionType)) {
        methods[actionType] = (payload) => {
          setState(state => {
            try {
              const _state = _.cloneDeep(state);
              actions[actionType](_state, payload);
              return _state;
            } catch (e) {
              console.error(e);
              return state;
            }
          });
        };
      }
    }

    return methods;
  }, [actions]);

  const CtxModelProvider = useMemo(() => {
    const Context = model.getContext();
  	const Provider = Context.Provider;
    const value = {
      ...state,
      ...mapActionsMethods
    };
    return ({ children }) => (
      <Provider value={value}>{children}</Provider>
    );
  }, [model, state, mapActionsMethods]);

  return {
    ...state,
    ...mapActionsMethods,
    CtxModelProvider
  };
}

export default useCtxModel;
  1. 封装 useCtxModelContext
js 复制代码
import { useContext } from 'react';

function useCtxModelContext(model) {
  return useContext(model.getContext());
}

export default useCtxModelContext;
  1. 封装 CtxModel
js 复制代码
import { createContext } from 'react';

const _state = Symbol('_state');
const _actions = Symbol('_actions');
const _Context = Symbol('_Context');

class CtxModel {
  constructor({ state, actions }) {
    this[_state] = state;
    this[_actions] = actions;
    this[_Context] = createContext({
      // 暂时不给值 ...
    });
  }

  getState() {
    return this[_state];
  }

  getActions() {
    return this[_actions];
  }

  getContext() {
    return this[_Context];
  }

  getModelConsumer() {
    return this[_Context].Consumer;
  }
}

export default CtxModel;

参考示例:📎react-model-hook.zip

总结:

  1. React 是一个非常灵活的 JavaScript 视图库,在一些特殊情况下,不一定需要引入 store、center 之类的操作概念。
  2. React 的跨组件共享几乎都是基于 Context API,state 可以自由发挥设计。
相关推荐
小小小小宇16 分钟前
TS泛型笔记
前端
小小小小宇20 分钟前
前端canvas手动实现复杂动画示例
前端
codingandsleeping22 分钟前
重读《你不知道的JavaScript》(上)- 作用域和闭包
前端·javascript
小小小小宇1 小时前
前端PerformanceObserver使用
前端
zhangxingchao2 小时前
Flutter中的页面跳转
前端
烛阴2 小时前
Puppeteer入门指南:掌控浏览器,开启自动化新时代
前端·javascript
全宝3 小时前
🖲️一行代码实现鼠标换肤
前端·css·html
小小小小宇3 小时前
前端模拟一个setTimeout
前端
萌萌哒草头将军4 小时前
🚀🚀🚀 不要只知道 Vite 了,可以看看 Farm ,Rust 编写的快速且一致的打包工具
前端·vue.js·react.js
芝士加4 小时前
Playwright vs MidScene:自动化工具“双雄”谁更适合你?
前端·javascript