mobx及mobx-react的简单实现

2022-04-19  本文已影响0人  Mr无愧于心
  1. Mbox的observable的方法主要实现的是对状态的深度代理
    (mobx4对应Object.defineProperty(),mobx5对应new Proxy())
    <类似于vue2、vue3的数据监听原理>
  2. 在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

  1. 包装对象值的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);
}
  1. 实现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;
    }
}

  1. 实现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的对比

上一篇下一篇

猜你喜欢

热点阅读