积梦前端采用的 React 状态管理方案: Rex

积梦(https://jimeng.io) 是一个为制造业制做的一个平台.
积梦的前端基于 React 作开发的. Rex 是咱们在前端使用的状态管理方案, 相似 Redux.
从名字也能够看, Rex 是一个基于 Redux 作了大幅简化的方案.
另外一方面, Rex 跟 Immer 有比较好的整合, 可以很轻松得使用不可变数据.前端

先前的技术方案

在开发 Rex 以前, 咱们主要采用了 mobx-state-tree 的方案, 以及试验过 Redux.
最先的代码使用了 mobx 搭配 mobx-state-tree, 比较迎合 observe 的用法.
可是使用 mobx 全家桶遇到了一些比较困扰的问题,git

  • mobx 对数据封装的话,数据量比较大的时候初始化很是慢, 对应图表.
  • observable 数据调试很不方便, 打印在 Console 是一个难以读取的对象.
  • mobx-state-tree 内置了 types, 加上偶尔有改版, 常常出现不可控, 好比报错, 字段修改.

因为我一直就是 immutable 数据的支持者, 就一直在试验可否用不可变数据解决这些问题.
可是早先主要是 immutablejs 方案, 按照之前的使用经验, 成本比较高.
后来出现了 immer, 在工业聚当有 Micheal 的介绍下咱们开始局部尝试, 取得了不错的效果.
并且由于 immer 也是 mobx 全家桶做者 Micheal 发布的模块, 使用也比较顺畅.github

最初我尝试过用 immer 搭配 Redux 来局部替换一些全局状态,
试验以后我以为效果上没有达到预期,npm

  • Redux 的 action/dispatch 在 JavaScript 当中没有足够灵活,
    在函数式语言好比 Clojure 当中, 一切解释表达式, 默认不可变数据, 处理 action 很是顺畅,
    可是用 JavaScript, 加上 immer 以后好一些, 但仍是须要显式地处处引入 immer.
  • TypeScript 类型跟 Redux 配合比较麻烦, 须要很是明肯定义好各个 Action.
    在 ReasonML 当中用代数类型很容易定义不一样的 Action, 而 TypeScript 相对繁琐.
    并且早先由于代码处理不干净, 类型推断并非生效, 影响了实际体验.

因此 Redux 方案没有按预期地推动下去.bash

Rex 的特色

其实无论 Redux 仍是 mobx-state-tree. 我想要的仍是状态透传的功能.
经过 @connect(() => {}) 来封装组件, 让局部能得到访问全局状态的能力.
至于具体的数据操做, immer 已经作到咱们能够接受的程度了.异步

后来在知乎看到过别人模仿 Redux 开发的类库, 我萌生了本身裁剪 Redux 代码的想法.
在同事的帮助下优化了 decorator 部分的代码, 我大体梳理出这样一个类库,ide

  • 基于 Context 实现 @connect() 的语法, 进行数据透传,
  • 用 immer 维护全局数据, 而且暴露出方便使用的方法.
  • 基于之前的代码, 大体处理好监听状态改变的的逻辑.
  • 生成 TypeScript 使用的类型文件.

Rex 的使用

目前 Rex 通过半年多的使用验证, 大体已经趋向稳定, 代码在 GitHub 上能够查看,
https://github.com/jimengio/rex
或者经过 npm 安装到本地,函数

npm install @jimengio/rex

使用 Rex 首先就是要定义全局状态的结构, 好比:工具

export interface IGlobalStore {
  obj: {
    a: number;
  };
  b: string;
}

export let initialStore: IGlobalStore = {
  obj: { a: 2 },
  b: "b"
};

而后初始化一个 globalStore, 包含该状态:性能

import { createStore } from "@jimengio/rex";

export let globalStore = createStore<IGlobalStore>(initialStore);

这里若是你想获取 store 的状态, 经过一个方法来读取,

globalStore.getState()

以及监听 store 的改变, 处理重绘:

globalStore.subscribe(() => {
  // rerender
});

当你要对数据进行操做时, 有两个方法可使用, updateupdateAt.
这两个方法直接将 immer 封装在内, 虽然是赋值操做, 但其实是不可变数据,
若是你对 immer 有疑问, 请仔细阅读它的文档并自行试验 https://github.com/mweststrat...
其实 updateAtupdate 的语法糖, 对于特定分支的数据的修改相对方便:

export function doIncData() {
  globalStore.update((store) => {
    store.b = "modified data";
  });

  globalStore.updateAt("obj", (obj) => {
    obj.a += 1;
  });
}

这个抽象大体从 Clojure 的 Atom 借鉴, 数据并不能直接修改.
若是你要修改状态, 就须要发送一个函数给 Rex, 而后 Rex 会在内部进行修改.

此外也提供了 RexProvider connectRex useRexContext 等函数, 跟 Redux 习惯尽可能一致.
基于这些函数, 就能实现一个简单的状态管理, 以及数据更新的透传.

一些须要注意

Rex 实现较为简单, 目前没有作更深层的优化, 也在避免引入新概念.
实际使用除了一些模板代码, 主要是 immer 的不可变数据须要注意.
immer 使用的是可变数据的写法, 可是内部经过 proxy 机制进行了转化, 达到不可变数据的效果.
平时写代码的时候须要注意区分可变和不可变的数据, 比较写法上是相似的.

另外要注意上面好比 doIncData 函数, 若是内部包含异步操做, 须要注意,
Rex 并不能支持异步, 因此在异步事件先后, 都须要直接调用 .update(),
也就是说对应会有两个(甚至多个)更新事件, 界面会更新屡次.

Rex 封装时, 考虑到性能问题, 作了一些基本的 shouldComponentUpdate 检测.
不过对于 useRexContext 这个 Hooks 的写法. 目前没有想明白怎么样处理, 须要手动处理.

其余

其余关于积梦前端的模块和工具能够查看咱们的 GitHub 主页 https://github.com/jimengio .
招聘的计划和条件也在 GitHub 上有给出 https://github.com/jimengio/h... .

相关文章
相关标签/搜索