![463a82cb0f9916e8f440085a042c7032.png](https://i-blog.csdnimg.cn/blog_migrate/1e20cb9f23180bf3786437fd9fdcfe59.jpeg)
Introduction
近些年,从Redux开始,已经出现了大量关于状态管理实现方案;比如React-Redux, Mobx, React-easy-state, Rematch, react-tracked... 还有蚂蚁比较官方应用的framework dva ;从另一个方面也体现出来了随着前端逻辑复杂度的提高,对于如何进行数据层的高性能处理越发显得重要。全局的状态在一定程度上解决了prop drilling;同时合理的使用,在组件的re-render上也能够有很大的性能提升
通过对比会发现,状态管理在所需属性的收集上主要分为两种
- 声明型:以Redux为例,通过mapStateToProps 函数来显示的声明当前组件所具体需要的属性。
- 响应式:以Mobx为例,它通过inject的方式注入store,但是具体到组件需要哪些属性,Mobx通过Observable的机制,自动进行绑定。
通过上面的简单对比会发现,在使用上Mobx会更简单一些。这个也是目前业内比较一致的观点。同时这个也是reactive programming带来的利好,自动实现访问属性的绑定。所以,在中间的一段时间,也在团队进行过Mobx的推广,但是随着业务的迭代以及人员的迭代,你会发现响应式更新方式,虽然在写法上很便捷,但是会使组件层面的渲染变的不是那么可控。
Michel Weststrate将Mobx的运行机制比作spreadsheet:一个值的更改,自动触发关联对象以及关联的衍生对象的更改。一个美好的愿景,但是也正是它的灵活性,新人加入时,会有一定的适应曲线。同时还有一个问题是,因为是自动更新的,在一定程度上,希望延迟更新会存在问题(比如长列表共同引用的一个属性,它的变化,为了性能的考量并不一定需要马上所有的item进行更新)
Why Relinx
随着对Reactive Programming理解的加深,尝试完成了Relinx的开发实现。Relinx在写法上主要有下面几个特点
- 概念上遵照社区的习惯,同时在结构上很大的程度借鉴dva;目前Relinx包含action, reducers, effects, model, dispatch等概念。所以上手程度非常简单
- 在组件中通过Proxy object,自动将访问的属性路径进行收集
- 通过dispatch的方式对base data进行更改,application会根据通过Paths创建的PathNodeTree定向更新对应的组件
整个的设计理念可以通过下面的图片获得比较直观的理解
![34dd73b8db0b0ba63b6d229e1e2ce7f5.png](https://i-blog.csdnimg.cn/blog_migrate/2b1d4194c8072c7528d4fa8511138177.jpeg)
- 一个observed组件会创建一个tracker和一个patcher,tracker是用来收集它所访问到的属性路径;patcher是用来在路径上的值发生变化时触发组件的更新
- Patcher会被添加到顶层的application,根据它的paths,application会创建一个PathNodeTree
- Dispatch触发state的更新,而更新的值会被传递给application
- 在application中,基于PathNodeTree进行diff比较;如果说一个pathNode上的值发生了变化,那么它对应的patchers就不被加入到pendingPatchers
- 当diff结束并且完成了patcher的clean up以后,pendingPatchers会被触发执行,那么patcher对应的组件就会重新渲染
- 组件重新渲染的时候,tracker会触发paths的更新,paths的更新会同步到patcher;最新的patcher又会被加入到application更新PathNodeTree,以等待接下来的数据更新监听
如何进行Path的收集
Tracker
Tracker是基于Proxy和defineProperty来实现的自动收集访问属性路径的模块。它会对base data进行getter trap的封装,
const base = {
a: {
b: 1,
},
c: {
d: 2,
e: [{ f: 1 }],
},
g: {
h: { i: 4 },
j: 5,
k: 6,
}
}
const baseNode = Tracker({ base: base.a })
const b = baseNode.proxy.b
baseNode.proxy.runFn('getRemarkableFullPaths') // [['b]]
const childNode = Tracker({ base: base.c })
const c = childNode.proxy.d
const f = childNode.proxy.e[0].f
const item = childNode.proxy.e[0]
childNode.proxy.runFn('getRemarkableFullPaths') // [['e', '0'], ['e', '0', 'f'], ['d']]
const grandChildNode = Tracker({ base: base.g })
const i = grandChildNode.proxy.h.i
const ff = item.f
grandChildNode.proxy.runFn('getRemarkableFullPaths') // [['h', 'i'], ['e', '0', 'f']]
概念上,Tracker和TrackerNode是一样的;一个TrackerNode可以包含多个TrackerNode;TrackerNode包含一个proxy属性,如果property value是一个数组或者对象的话,每一次的访问它都会判断是否需要创建一个新的child proxy然后将这个child proxy返回,从而实现对嵌套的对象中属性路径的记录。具体的数据结构如下
![eb27018a5c12bdac16d12ab5040e3dd1.png](https://i-blog.csdnimg.cn/blog_migrate/bca06dabc046be5605705bfb5d7789de.jpeg)
application如何运行
PathNode
首先Tracker会完成paths的收集,然后会将收集到的paths更新patcher;patcher会被添加到application。然后application根据patcher的paths创建PathNode。如下
![e90f14a86690d32aa3349ffc40be6887.png](https://i-blog.csdnimg.cn/blog_migrate/81b8e9baf60f22117504970aa7e01a5e.jpeg)
通过实际场景进行运行机制的描述
const store = {
a: {
b: 1
},
c: {
d: 2,
e: [{f: 1}]
},
g: {
h: {i: 4},
j: 5,
k: 6,
}
}
const A = observe(() => {
const [state] = useRelinx('a')
const b = state.b
return (
<div>
<span>{b}</span>
<C />
</div>
)
})
const C = observe(() => {
const [state] = useRelinx('c')
const { d, e } = state
return (
<div>
<span>{d}</span>
{e.map(item => <G item={item} />)}
</div>
)
})
const G = observe(props => {
const [state] = useRelinx('c')
const { h: { i }} = state
const { item } = props
return (
<div>
<span>{i}</span>
<span>{item.f}</span>
</div>
)
})
比如上面是一个比较简单的例子,其中observe,useRelinx可以到Relinx进行查阅。
当dispatch一个action时,reducer会计算出changed value group;然后application会根据PathNodeTree进行diff比较
![7f09fd392be4d1793f77f13b4aa1b645.png](https://i-blog.csdnimg.cn/blog_migrate/c9ff63dcde663fafda72b9c62ba7efb3.jpeg)
1:当节点i的值放生变化时,它会将节点i对应的patchers [G]加入到待执行数组pendingPatchers中;同时会将[G] patchers从其他节点中删除以避免内存泄漏
2:当节点e被更新为空数组时,它会将length节点的patchers加入到待执行数组,同时会在比较结束以后,将整个Pathnode进行销毁。
总结
在进行Tracker的实现上,很大程度的借鉴了immer对trap的使用方式。在代码实现上借鉴了functional reactive programming library 比如meteor - tracker和xstream. 希望通过这个项目可以在状态管理以及trap的使用上对其它开发者有一个其它角度的理解
如果有兴趣可以去 A fast, intuitive, access path based react state management - Relinx 了解更多实现原理