谈谈mobx的实现

280 阅读3分钟

observable的实现机制

mobx的observable能够实现数据的观测,那内部是怎么实现数据的观测的呢

import {observable} from './mobx'
let dl = observable({name:'lrm'});
console.log(dl);

这里我手动创建了一个react的工程

手动引入自己创建的mobx的index文件,引入创建的observable文件

  function deepProxy(value,handler) {
    if(typeof value !== 'object') return value;
    //如果是对象走代理,代理的第一个是值,第二个是对象
    return new Proxy(value,handler());
}
function createObservable(val){
    //声明一个专门用来代理的代理对象
    let handler=()=>{
        return {
            set(target,key,value){
                return Reflect.set(target,key,value)
            },
            get(target,key){
                return Reflect.get(target,key)
            }
        }
    }
    return deepProxy(val,handler)
}
const observable=(target)=>{
    return createObservable(target)

}
export default observable;     

可以实现同样的效果

import {observable} from './mobx'
let dl = observable({name:'lrm',age:{num:18}}});
console.log(dl); 

  

这时候我们发现如果对象只是一层可以实现,但是如果是多层就没有代理了,那该如何改进:

针对多层次的代理可以进行遍历再代理

               

    function deepProxy(value,handler) {
    if(typeof value !== 'object') return value;
     for(let key in value) {
        value[key] = deepProxy(value[key],handler);
    }
    //如果是对象走代理,代理的第一个是值,第二个是对象
    return new Proxy(value,handler);
}
function createObservable(val){
    //声明一个专门用来代理的代理对象
    let handler=()=>{
        return {
            set(target,key,value){
                return Reflect.set(target,key,value)
            },
            get(target,key){
                return Reflect.get(target,key)
            }
        }
    }
    return deepProxy(val,handler)
}
const observable=(target)=>{
    return createObservable(target)

}
export default observable;//对多层次的代理,需要遍历代理 

    

autorun:当使用autorun时,所提供的函数总是立即被触发一次,然后每次它的依赖关系改变时候会再次被自动触发

import {observable,autorun} from './mobx'
let dl = observable({name:'lrm',age:{num:18}});
console.log(dl.age);
autorun(()=>{
    console.log(dl.age.num);
})       

这时候拿到的值是18,如果我想改变ad.age.num,不会直接触发改变,需要做一个依赖收集,触发改变。

比如我在autorun后面加上dl.age.num = 100;,想让antorun打印的值是18,100怎么办

在 aotorun中 reaction相当于一个中间的垫片,执行reaction.end()方法是为了清空,防止下一次使用值被污染。

import Reaction from './reaction';
let autorun = (handler)=>{
    Reaction.start(handler);//先保存了这个函数
    handler();  //调用这个方法触发get属性
    Reaction.end();
}
export default autorun;   

 在reaction.js中的实现思路如下:           

let nowFn = null; //当前的autorun方法
let couter = 0;//为了保证new的实利是唯一的
class Reaction{
    constructor(){
        this.id= ++couter;
        this.store={};//存储当前可观察对象nowFn{id:[nowFn]}
    }
    run(){
        //拿到里面的属性让跑一遍,让依赖的值执行
        if(this.store[this.id]){
            this.store[this.id].forEach(w=>{
                w();
            })
        }
    }
    collect(){
        //当前有需要绑定的函数才进行绑定,没有先不理
        if(nowFn){
            this.store[this.id] = this.store[this.id] ||[];
            //调用的时候做一个对应关系
            this.store[this.id].push(nowFn);
        }
    }
    static start(handler){
        nowFn = handler;
    }
    static end(){
        nowFn = null;
    }
}
export default Reaction;     

在autorun文件中引入start和end方法,获取改变之后的值是在observa的get中进行获取:        

  function createObservable(val){
    //声明一个专门用来代理的代理对象
    let handler=()=>{
        let reaction = new Reaction();
        return {
            get(target,key){
                reaction.collect();
                return Reflect.get(target,key)
            },
            set(target,key,value){
                let r = Reflect.set(target,key,value);
                reaction.run();
                return r;
            },
           
        }
    }
    return deepProxy(val,handler)
}
const observable=(target)=>{
    return createObservable(target)

}

这样我们在get时候收集,set时候执行:

在index文件中再次执行:

import {observable,autorun} from './mobx'
let dl = observable({name:'lrm',age:{num:18}});
console.log(dl.age);
autorun(()=>{
    console.log(dl.age.num);
})
dl.age.num = 100;  

  这时候在浏览器的运行结果是: