没有 Redux,让全局数据更简单

1,561 阅读4分钟

笔者是一个有5年开发经验的前端工程师。从接触前端就一直使用 Redux,对 Redux 也算有点心得。今天分享个小 Tip 给大家 😌。

慢慢臃肿的 Redux Store

从欣赏到慢慢嫌弃

Redux 的存在让前端开发变得简单,这点毫无疑问,否则也不会有那么多人用它。在项目初建时,Redux 中只有一两个全局状态,一切看起来都很舒服。

但是,慢慢地,页面复杂起来了。页面A中有个状态在多个组件中需要复用。

一把梭,几下复制粘贴就把这个状态放到 Redux Store 中了。暗笑:Redux 真棒。

很快,页面B也有状态需要多个组件共用了。不用想,直接梭它。

页面A、页面B又来了,页面C也来了。梭梭梭....

Store 就成这样了。

好丑,好难过,不过问题不大。

在某个周一的上午,迎来了新的需求,需要页面 B 能访问并修改 pageA.A1.A1A 中的数据。

一把梭,在页面 B 中写上 state => state.pageA.A1.A1A,再调用修改 pageA 的相关 action。虽然完美实现了需求,但是每次看到这段代码,总觉得不舒服。它们不但丑(因为数据链路长),还别扭(因为 pageB 访问了 pageA 中的数据)。

也许是我用法不对

开始反思,是否有更好的解法。于是开始重新组织数据的结构,开启平铺模式。

甚至觉得按照页面来组织 store 不优雅,应该按照领域模型来组织,将所有数据都平铺了。

不得不说,全部平铺还是很有优势的,访问是路径变短了,状态只跟领域模型有关,与页面无关了。除了命名上麻烦点。

这也是 dva 的 store 只有一层 model 的原因。但实际情况并没有变理想,多数时候一个 model 中有很多不相关的 state,比如复用后端某个接口的返回值。

找到根本原因

将状态放在 Redux Store 中,需要如下几步:

  1. 写好 model,将它注册到 store 中。注册到 store 的文件开始冗长,经常要解决冲突 😭。
  2. 访问 store 中的数据,需要不断的 state.A.B,如果没有类型文件链路长了容易出错。
  3. 更新某个状态调用,dispatch('updateA_B'),关键是这里的 type 是一个字符串。rematch 解决了这个问题。

想想如果不用把状态集中放到一个 store 中,而只将它存放在模块级别。访问数据时只需引入该模块暴露的 store,修改数据时只需调用该模块暴露的方法。这个模块想放哪就放哪,不用集中放在 Redux Store 目录。

尝试新的工具 use-global-hook

仓库地址:use-global-hook

基本用法

使用时只需新建一个文件存放 model,参考官网 Example

import React from 'react';
import globalHook from 'use-global-hook';

const initialState = {
  counter: 0,
};

const actions = {
  addToCounter: (store, amount) => {
    const newCounterValue = store.state.counter + amount;
    store.setState({ counter: newCounterValue });
  },
};

export const useGlobal = globalHook(React, initialState, actions);

然后在需要复用该状态的组件中,只需引用该 model 即可。import React from 'react';

import React from 'react';
import { useGlobal } from './useGlobal';

const App = () => {
  const [globalState, globalActions] = useGlobal();
  return (
    <div>
      <p>
        counter:
        {globalState.counter}
      </p>
      <button type="button" onClick={() => globalActions.addToCounter(1)}>
        +1 to global
      </button>
    </div>
  );
};

export default App;

通过 mapState 性能优化

参考:官方例子

通过 Immer 简化状态更新

参考:官方例子

通过暴露 getState 处理多个 model 存在依赖更新

没有官方例子,属于个人总结。先给每个 model 都增加一个 action,其代码如下。

function getState(store) { return store.state; }

modelB 更新依赖最新的 modelA 的值。

function useHandleAB() {
  const [stateA, { updateA, getState: getStateA }] = useModelA();
  const [stateB, { updateB }] = useModelB();

  return function handleAB(opts) {
    updateA(opts);
    // 此时 stateA 已经不等于 getStateA() 了
    updateB(getStateA(), opts); // 更新 modelB 依赖 modelA 的值
  };
}

总结

Redux 的状态集中管理,其缺点有:

  1. 其 reducer 的组织方式一直有争论,原因就是集中式的管理导致 Store 变得冗长。
  2. 需要将所有状态进行注册,访问时又需要从 state 中取出。
  3. actionType 字符串的存在。

use-global-hook 通过将模型 store 存在模块级别。其模块位置可以随意放置,更灵活。省略了状态注册和取出,更简介。其 actions 也是以普通方法存在,更自然。