在 React 中引入 Vue3 的 reactivity 分包来实现状态管理。
前言
React 的状态管理是一个缤纷繁杂的大世界,光我知道的就不下数十种,其中有最出名 immutable 阵营的redux,有 mutable 阵营的mobx,react-easy-state,在 hooks 诞生后还有极简主义的unstated-next,有蚂蚁金服的大佬出品的hox、hoox。
其实社区诞生这么多种状态管理框架,也说明状态管理库之间都有一些让人不满足的地方。
rxv是我依据这些痛点,并且直接引入了 Vue3 的 package: @vue/reactivity去做的一个 React 状态管理框架,下面先看一个简单的示例:
示例
// store.ts import { reactive, computed, effect } from '@vue/reactivity'; export const state = reactive({ count: 0, }); const plusOne = computed(() => state.count + 1); effect(() => { console.log('plusOne changed: ', plusOne); }); const add = () => (state.count += 1); export const mutations = { // mutation add, }; export const store = { state, computed: { plusOne, }, }; export type Store = typeof store;
// Index.tsx import { Provider, useStore } from 'rxv' import { mutations, store, Store } from './store.ts' function Count() { const countState = useStore((store: Store) => { const { state, computed } = store; const { count } = state; const { plusOne } = computed; return { count, plusOne, }; }); return ( <Card hoverable style={{ marginBottom: 24 }}> <h1>计数器</h1> <div className="chunk"> <div className="chunk">store 中的 count 现在是 {countState.count}</div> <div className="chunk">computed 值中的 plusOne 现在是 {countState.plusOne.value}</div> <Button onClick={mutations.add}>add</Button> </div> </Card> ); } export default () => { return ( <Provider value={store}> <Count /> </Provider> ); };
可以看出,store的定义只用到了@vue/reactivity,而rxv只是在组件中做了一层桥接,连通了 Vue3 和 React,正如它名字的含义:React x Vue。
源码地址
https://github.com/sl1673495/react-composition-api
一些痛点
根据我自己的看法,我先简单的总结一下现有的状态管理库中或多或少存在的一些不足之处:
- 以
redux为代表的,语法比较冗余,样板文件比较多。 mobx很好,但是也需要单独的学一套 api,对于 react 组件的侵入性较强,装饰器语法不稳定。unstated-next是一个极简的框架,对于 React Hook 做了一层较浅的封装。react-easy-state引入了observe-util,这个库对于响应式的处理很接近 Vue3,我想要的了。
下面展开来讲:
options-based 的痛点
Vuex 和 dva 的options-based的模式现在看来弊端多多。具体的可以看尤大在vue-composition-api 文档中总结的。
简单来说就是一个组件有好几个功能点,但是这几个功能点在分散在data,methods,computed中,形成了一个杂乱无章的结构。
当你想维护一个功能,你不得不先完整的看完这个配置对象的全貌。
心惊胆战的去掉几行,改掉几行,说不定会遗留一些没用的代码,也或者隐藏在 computed 选项里的某个相关的函数悄悄的坑了你…
而 hook 带来的好处是更加灵活的代码组织方式。
redux
直接引入 dan 自己的吐槽吧,要学的概念太多,写一个简单的功能要在五个文件之间跳来跳去,好头疼。redux 的弊端在社区被讨论也不是一天两天了,相信写过 redux 的你也是深有同感。

unstated-next
unstated-next 其实很不错了,源码就 40 来行。最大程度的利用了 React Hook 的能力,写一个 model 就是写一个自定义 hook。但是极简也带来了一些问题:
- 模块之间没有相互访问的能力。
- Context 的性能问题,让你需要关注模块的划分。(具体可以看我这篇文章的性能章节)
- 模块划分的问题,如果全放在一个 Provider,那么更新的粒度太大,所有用了 useContext 的组件都会重复渲染。如果放在多个 Provider 里,那么就会回到第一条痛点,这些模块之间是相互独立的,没法互相访问。
- hook 带来的一些心智负担的问题。React Hooks 你真的用对了吗?
react-easy-state
这个库引入的observe-util其实和 Vue3 reactivity 部分的核心实现很相似,关于原理解析也可以看我之前写的两篇文章:
带你彻底搞懂 Vue3 的 Proxy 响应式原理! TypeScript 从零实现基于 Proxy 的响应式库。
带你彻底搞懂 Vue3 的 Proxy 响应式原理!基于函数劫持实现 Map 和 Set 的响应式。
那其实转而一想,Vue3 reactivity 其实是observe-util的强化版,它拥有了更多的定制能力,如果我们能把这部分直接接入到状态管理库中,岂不是完全拥有了 Vue3 的响应式能力。
原理分析
vue-next是 Vue3 的源码仓库,Vue3 采用 lerna 做 package 的划分,而响应式能力@vue/reactivity被划分到了单独的一个 package 中
从这个包提供的几个核心 api 来分析:
effect
effect 其实是响应式库中一个通用的概念:观察函数,就像 Vue2 中的Watcher,mobx 中的autorun,observer一样,它的作用是收集依赖。
它接受的是一个函数,这个函数内部对于响应式数据的访问都可以收集依赖,那么在响应式数据更新后,就会触发响应的更新事件。
reactive
响应式数据的核心 api,这个 api 返回的是一个proxy,对上面所有属性的访问都会被劫持,从而在 get 的时候收集依赖(也就是正在运行的effect),在 set 的时候触发更新。
ref
对于简单数据类型比如number,我们不可能像这样去做:
let data = reactive(2) //