mobx及mobx-react的简单实现
2022-04-19 本文已影响0人
Mr无愧于心
- Mbox的observable的方法主要实现的是对状态的深度代理
(mobx4对应Object.defineProperty(),mobx5对应new Proxy())
<类似于vue2、vue3的数据监听原理> - 在autorun方法使用的时候,会在这个代理过程中执行状态的依赖收集的相关操作,触发对应的handle函数。
import { observable, autorun } from "./mobx"
// observable方法用于完成对状态对象的Proxy的代理,将状态变为可观察对象
const o = observable({ name: "1" });
// autorun方法类似于vue中的watcher,其中传递进去一个handler Function,
// 这个回调函数会在初始化的时候被执行一次,之后每次内部相关的observable中的依赖发生变动时被再次调用
autorun(() => {
console.log(o.name)
})
// 直接设置 o.name 值
o.name = "2"
// 打印: 1
// 打印: 2
实现observable和autorun
- 包装对象值的Observable,核心原理是Object.defineProperty(),给被包装的属性套上get和set钩子,在get中收集依赖,在set上触发监听函数。
// 这里后面的两个参数: 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){
// 执行搜集绑定, 此时修改值需要先执行,这样在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-react的@observer
var ReactMixin = {
componentWillMount: function() {
autorun(() => {
this.render();
this.forceUpdate();
});
}
};
function observer(target) {
const targetCWM = target.prototype.componentWillMount;
target.prototype.componentWillMount = function() {
targetCWM && targetCWM.call(this);
ReactMixin.componentWillMount.call(this);
};
}
mobx和redux的对比
-
相同的地方
mobx 和 redux 都是单向数据流,通过 action 触发全局 state 更新,然后通知视图。 -
不同的地方
- 修改状态的方式不同,
react每次都是修改同一个状态对象,基于响应式代理,也就是Object.defineProperty代理get\set的处理,get时把依赖收集起来,set修改时通知所有的依赖做更新
redux是每次返回一个全新的状态 - 管理状态的思路上
redux 那种方式是函数式的思路,所以状态的修改都在一个个 reducer 函数里每次返回新的state,而 mobx 那种方式则是面向对象的代理的思路,所以很容易把 state 组织成一个个 class - 性能方面
mobx 的响应式能精准的通知依赖做更新,而 redux 只能全局通知,而且 mobx 只是修改同一个对象,不是每次创建新对象,性能会比 redux 更高
- 修改状态的方式不同,