手写状态管理库: Mobx
mobx是一个状态管理库,类似于redux或者vuex完成状态观察相应相关的处理。mobx使得状态管理更加的简单和透明。由于这篇文章时mobx原理解析,所以关于mobx的使用就不在这里记录。
注: 在开始之前,需要先搭建起支持Es7语法的装饰器开发的webpack等开发环境. 这里主要实现Mobx的observable, autorun两个方法.
Mbox的observable的方法主要实现的是对状态的Proxy深度代理,使用Proxy对象代理状态对象的属性,同时在autorun方法使用的时候,会在这个代理过程中执行状态的依赖收集的相关操作.
首先这里给一个简单的测试例子:
// 测试 Mobx 库用例
import { observable, autorun } from "./mobx"
// observable方法用于完成对状态对象的Proxy的代理,将状态变为可观察对象
const o = observable({ name: "chensir" });
// autorun方法类似于vue中的watcher,其中传递进去一个handler Function,这个回调函数会在初始化的时候被执行一次,之后每次内部相关的observable中的依赖发生变动时被再次调用
autorun(() => {
console.log(o.name)
})
// 直接设置 o.name 值
o.name = "chenSirLaiLe"
// 测试用例会打印: chensir \n chenSirLaiLe
实现observable方法:
// 这里后面的两个参数: key 和 descriptor主要用于之后的装饰器实现
export defualt function observable(target, key, descriptor){
// 这里支持装饰器模式的observable写法:
if(typeof key === "string"){
// 如果是作为装饰器装饰属性进行监听,先将装饰的对象进行深度代理
let v = descriptor.initializer();
v = createObservable(v);
// 这里执行依赖搜集: 使用的Reaction类会在之后实现
let reaction = new Reaction();
// 返回描述器
return {
enumerable: true,
configurable: true,
get(){
reaction.collect(); // 再获取target属性时进行autorun中的handler的依赖搜集
return v;
},
set(value){
v = value;
reaction.run(); // 在每次更新target中的属性时执行autorun中的依赖
}
}
}
// 如果不是装饰器写法,则创建Proxy代理
return createObservable(target);
}
创建代理对象
function createObservable(val){
// 用于生成代理对象的控制器:
const handler = () => {
// 实例化Reaction在autorun获取属性的时候进行依赖搜集
let reaction = new Reaction();
return {
set(target, key, value){
// 对于数组的值设置处理: 当对数组进行观察监听时,由于对数组的操作会有两步执行:
// 更新数组元素值
// 更改数组的length属性,所以需要将更改length属性的操作给拦截,避免一次操作数组,多次触发handler
if(key === "length"){
return true;
}
// 执行搜集绑定, 此时修改值需要先执行,这样在autorun中的handler中才能拿到最新的值
let r = Reflect.set(target, key, value)
reaction.run();
return r;
},
get(target, key){
// 在获取属性值的时候进行依赖搜集
reaction.collect()
return Reflect.get(target, key);
}
}
}
// 进行深层Proxy代理返回: 针对如: {name: "chensir", age: {num: 21}}这样的对象
return deepProxy(val, handler)
}
// 深度设置Proxy对象代理
function deepProxy(val, handler){
if(typeof val !== "object"){
return val;
}
// 深度递归进行Proxy代理,此时的递归树相当于是后序遍历进行代理
for(let key in val){
val[key] = deepProxy(val[key], handler);
}
return new Proxy(val, handler);
}
实现Reaction类进行状态搜集,作为abservable和autorun之间的桥梁:
// 定义两个全局变量,这里是简单实现,所以和实际的源码实现有一定的区别
let nowFn = null; // 这个表示当前的autorun中的handler方法
let counter = 0; // 这里使用counter记录一个计数器值作为每个observable属性的id值进行和nowFn进行绑定
class Reaction {
constructor(){
// 标识每一个proxy对象
this.id = ++counter; // 这里采用一个比较low的方法简易实现的,在每次对observable属性进行Proxy的时候,对Proxy进行标记
this.store = {}; // 存储当前可观察对象对应的nowFn, 写入的形式如: {id: [nowFn]}
}
collect(){
// 进行依赖搜集,只当当前有autorun绑定了相关属性观察后才会进行绑定
if(nowFn){ // 通过这个判断主要是因为只有在调用autorun绑定的时候才会设置这里的nowFn
this.store[this.id] = this.store[this.id] || [];
this.store[this.id].push(nowFn);
}
}
run(){
// 运行依赖函数
if(this.store[this.id]){
this.store[this.id].forEach(fn => {
fn()
})
}
}
// 定义两个静态方法,用于在调用autorun方法时候对nowFn进行设置和消除
static start(handler){
nowFn = handler;
}
// 在注册绑定这个就要清空当前的nowFn,用于之后进行进行搜集绑定
static end(){
nowFn = null;
}
}
实现autorun方法,进行简单的依赖搜集
export default function autorun(handler){
if(typeof handler !== "function"){
throw new TypeError(`autorun function expect a function but get a ${typeof handler}`)
}
// 开始搜集依赖,设置Reaction中的nowFn
Reaction.start(handler)
// 执行一次handler,在handler中有对于相应属性的getter获取,此时就可以设置改属性的Proxy的Reaction状态依赖
handler()
// 清除nowFn
Reaction.end()
}
这里简单实现了Mobx的核心,当然和源码还是有一定的区别,所以不要将此处的代码和源码进行比对,我们只是希望去理解Mobx的状态观察搜集的原理而已。除此之外,Mobx还有一些其他的方法大家可以去了解了解。除此之外,Mobx在React框架中使用时,需要使用一个Mobx-react的库,这个库的实现原理也比骄简单这里提一下思路原理: Mobx-React这个库在React Component组件中的state发生变动时手动调起组件的render方法和forceUpdate()对外部mobx更新后的状态在Component中进行强制刷新。
转载自:https://juejin.cn/post/7076329672706883591