对现代主流框架状态管理库的思考
为什么
这里所指的状态管理库泛指,vue/react/angular
三大框架相关的状态管理库
状态管理在我看来其实是个非常宽泛的概念,我更愿意称它是个跨层级全局上下文共享配置。比如放在 class
中可以是 this
,放在服务端单机上叫 全局单例的class
,放集群上就是配置中心。这些放置的地方有的并不是特意作为存放共享配置用的,但是并不妨碍都可以做到共同的事情
时到今日聊这个话题,一是想分享自己的看法,二是也想请教下正在看这篇文章的你,是否有和我不同的看法,~欢迎以任何方式联系到我说出你的独道的见解
源头
第一次有想这个东西苗头,是几年前在我学了 react
之后,那时候我只会 vue
,也只接触过 vuex
, vuex
用起来很顺手,反观当时 react
最火的是 redux
,初学我不明白这东西为什么这么复杂,过了许久我自认为精通后,我开始不理解,老外为什么总能能把简简单单做到的事情,要硬套概念搞得这么繁琐
会这么想是因为我想到了基于 react
的另一种实现方式,也做出来了雏形,代码非常的少,所以写的很快
雏形
实现的基本原理就是 useState
+ 订阅发布者模式,它使用方式大致如下
首先创建使用给定函数创建初始数据
组件内使用时,我就在内部使用 useState
创建状态,然后收集返回的 setState
函数,返回创建函数的状态,以及模拟一个 set
函数返回给用户
这样每次当用户在任意用到的组件内修改状态时,我只需要把当前收集到的 setState
函数全部调用一遍。组件外访问则可以单独提供一个函数用于获取
const map = new Map()
function createStore(initState) {
const flag = Symbol()
map.set(flag, { state: initState, sets: new Set() })
return flag
}
function getStore(flag) {
return map.has(flag) && map.get(flag).state
}
function useStore(flag) {
const { state, sets } = map.get(flag)
const [s, set] = useState(state)
sets.add(set)
const _set = (v) => {
Object.assign(state, v)
sets.forEach(f => f(state))
}
return [s, _set]
}
//定义状态
const store = createStore({ msg: "这是初始状态", fn: ()=>1 })
//组件内使用
function App() {
const [store, setStore] = useStore(store)
return
}
//组件外使用
const store = getStore(store)
以上是简化版代码,表达个大概原理
虽然实现上非常的淳朴,但是也算做到了状态共享和响应式更新,其他的功能全都没有
如果有用过 jotai/ valtio/ zustand
的话,会发现原理和这种原子状态管理库是非常像的。之所以没有继续做下去是我觉得这是邪门歪道。本意是想找个简单的方式实现共享状态的响应式更新,因为用 redux
时我总是忘这破玩意应该怎么写,明明并不复杂的一个东西却搞很多模版代码。可如果放出来觉得就是找骂的,所以后来当 zustand
盛行后,我大为震惊
现代框架的状态管理
讲这个往事不是想表达,它们都是一群渣渣,我上我也行的炫耀。而是想说我曾经很早的时候就做过,这只是其中一个,因为做过所以了解些许其中的门道
状态管理本身是个大话题,我没资格讲它。但现代的框架的状态管理还是能唠唠的,这里我们可以尝试对比下,不同风格的状态管理库之间的共同点和差异性
代表框架的特点如下
框架名称 | 代码风格 |
---|---|
vue/pinia | 基于 vue 的响应式 api,更进一步则可以说是基于 proxy |
react/redux | 典型的订阅发布者模式,需要创建,映射,消费,卡修改流程... |
react/zustant | 原子状态管理库,get/set 风格 |
react/jotai | 原子状态管理库,proxy 的响应式风格 |
angular | 依赖注入 |
它们的共通点
- 全局组件可访问
- 响应式状态更新
- 有着各自的代码风格/规范(这个还真不是可有可无的东西!!!)
- 可扩展
- 可提供调试(例如 devtools 集成)
它们的差异点
- 实现风格不同
get/set
的函数流派proxy
的响应式流派
- 特色功能不同
- 例如有的能严格卡更新环节,例如
redux
,而基于proxy
的在这方面总会出现漏洞 - 使用便捷程度,基于
proxy
舒服的很
- 例如有的能严格卡更新环节,例如
细节上的东西会有很多,扣起来没完没了了,我这里只是列出来了宏观上的
把这些东西列出来后,就意味着,如果我们只要做到了共同点的部分,尤其是前两条,它就可算是一个最低限度的,生产可用的版本了
对管理库进行抽象
核心抽象
如果实现一个状态管理库本身在我看来并不难,甚至精简成 2 个词来总结
- 共享
- 管理
第一点很好理解,我们用管理库的本身可不就是为了跨组件共享状态么
第二点管理需要绕个弯子想,因为不好好想想就会觉得这东西是个可有可无的东西了
可能有人疑惑,全局响应式更新难道就不算是必备条件吗?状态管理库别管是不是官方出的,要实现响应式更新就得依赖框架的响应式系统,框架即便不提供,只要能共享状态,用户自己也能做到。之所以每个框架都提供相关的能力,不如想想如果不提供还有谁用啊!!!
如何管理
共享和一些默认的东西 (响应式更新)都是必备功能,而如何进行状态管理则是各家状态管理库真正的卖点所在
管理主要包含了两方面内容
- 如何对状态,从创建到消费,这之间各个环节进行卡点。比如提供生命周期,让用户的插件可以在修改状态前干点什么,修改状态后干点什么,典型的应用是持久化保存数据到
localstoreage
- 代码风格/规范。不同的风格规范直接决定了用户的使用形式和对外提供什么功能。比如
vue
的用户可曾想过,使用本身的响应式系统和使用pinia
之间的差异是什么吗?再比如redux
是没法做出来像jotai/pinia/vuex
一样的简单的 api 用么,那肯定是能做到的
概念套套套
弄清楚了管理库核心抽象后,我们在看看看 vue/react
生态中的相关库
Vue
vue
中应该会有很多人有疑问 pinia
到底该不该用,它和直接用 vue
的响应式系统,直接 reactive
一个对象用到底差异在了哪里
这个答案很简单,pinia
提供了默认的开发规范,如怎么定义状态、修改状态的位置和方式、使用状态的方式,反之直接用 reactive
则没有,我们需要自己定义,应用规模不大都用不到定义,只要不放飞自我影响都不大
功能上则是一模一样,所以对规范不看重的想要自定义团队规则的,完全可以自己定几条团队规则,这样反而比直接用 pinia
能剩下很多的打包体积,至于最大的损失,我觉得就是 devtools
了,但现在有多少人喜欢用这东西来调试的?
注:这个做法是尤雨溪亲口承认的,b 站有本人的访谈视频可以自行查找
React
react
的生态真的是百花齐放,相较于 vue
总是上来不管三七二十一的上 pinia
,我还是更喜欢 react
的这种模式。因为我不喜欢 vue/angular
官方提供了推荐,然后用户就无脑用,我不喜欢把简单的东西搞复杂,它们会产生诸多模板式的用法和无聊的八股文,我尤其恨死这方面的八股文,看着面试官一本正经问管理库的原理和使用心得,说的抽象点还不认,无语死
对于 react
生态的众多的管理库来说,用哪个学哪个无非是自己团队的更喜欢用哪种风格的代码,只要核心功能没 bug,代码写的轻松就够了,应该没什么人喜欢搞这方面的插件吧!!!像 redux 那样,一个小小的库,硬生生靠着各种扩展搞得功能五花八门的,新手直接哭晕在了厕所
注:越是复杂的项目,个人更推荐 get/set
风格的,反之则是基于 proxy
的
其他场景
这里我想着重来聊聊微前端,实现微前端的框架并不重要。在微前端下,一个页面会有若干个不同框架的项目共同工作,基于之前的抽象我们就可以套进来,做个非常简单的,生产也够用的版本,即我们只需要满足,共享+响应式更新就够了
比如我们可以在全局(尽量放在基座应用会更好,防止被攻击)搞个对象实例,然后提供 get/set
方法,提供它们是为了可以调试哪些应用操作了状态,想做的简单直接一个 new Map
也行。不建议用 proxy
代理,因为以外修改状态的漏铜太多,性能也差,想要做高质量 bug 少的在这里就不能用这玩意
在各自的子应用中,可以提供一个转换函数,该函数需要把状态塞进各自应用的状态管理库内部。然后双向监听管理库内部的变化以及全局库的变化,做到状态同步。虽然是实现起来不是很优雅,但确实最简单无脑的做法之一
转载自:https://juejin.cn/post/7311206584204525608