前言
React的状态管理是一个缤纷繁杂的大世界,光我知道的就不下数十种,其中有最出名immutable阵营的redux
,有mutable阵营的mobx
,react-easy-state
,在hooks诞生后还有极简主义的unstated-next
,有蚂蚁金服的大佬出品的hox
、hoox
。
其实社区诞生这么多种状态管理框架,也说明状态管理库之间都有一些让人不满足的地方。
rxv状态管理库
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;
复制代码
代码语言:javascript复制// 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}>addButton>
div>
Card>
);
}
export default () => {
return (
<Provider value={store}>
<Count />
Provider>
);
};
复制代码
可以看出,store
的定义只用到了@vue/reactivity
,而rxv
只是在组件中做了一层桥接,连通了Vue3和React,正如它名字的含义:React x Vue。
一些痛点
根据我自己的看法,我先简单的总结一下现有的状态管理库中或多或少存在的一些不足之处:
- 以
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)
//