vue08-ref实现原理02

56 阅读5分钟

类的属性访问器

// 当我对类进行访问的时候就会触发这个
// let A = new Ma();
// A.value;
// A.value = 1;
// 我们对象是通过get 或者 set来做这个事情的。
class Ma {
    set value() {
        /**
        * 需要触发trigger触发effect
        */
    }

    get value() {
        /**
        * 需要做track收集effect
        */
    }
}
toRef
// 把一个对象中的属性变成ref
// 这个内容怎么使用?
let state = {a:1};
// 把a变成toRef
let cref = toRef(state, 'a');
console.log(cref); // 直接就把a里面的值拿出来了变成了ref了
// __V_isRef:true 告诉你是一个ref了

// state如果是响应式 cref是响应式, 如果state不是响应式cref就不是响应式。


vue-core/package/reactivity/core/ref.ts

// 继续上一篇的讲解
/**
 * 
 * @param rawvalue 原值可以是对象,但一般情况下用reactive更好
 * @returns Object 这东西返回的是一个实例 
 */
function createRef(rawvalue:any, isShallow:boolean = false) {
    // 创建一个ref
    return new RefImpl(rawvalue, isShallow);
}


// 转换如果是对象用reactive包装一下如果是值不动
const cover = (val:any) => isObject(val) ? reactive(val) : val;

// husk做提交的一些钩子 和 reactive最本质的区别这个编译完了是defineProperty
class RefImpl {
    public _value:any = '';        // 被代理的值
    public _is_Ref:boolean = true; // 当前产生的实例是会被添加这个属性表示是一个ref属性。
    
    constructor(public rawvalue:any, public isShallow:boolean){
        // defineProperty在取值和设置值的时候都需要有个公共的值this._value这个就是我们要公共操作的。
        // 如果是一层的直接用值就行了如果是多层的需要里面的内容都是响应式的。
        // 在里面的ref会调用reactive
        
        // 如果是一层的直接用值,如果是多层的直接用响应式的。
        this._value = isShallow ? rawvalue : cover(rawvalue);
    
    }
    
    // [核心]
    // 访问的是value 返回的是_value代理   
    /**
    * 当 let r = ref("a");
    * effect(() => {
    *     r.value就会走到下面的位置
    * })
    */
    get value(){
        // 开始收集的模式
        // 开始收集this是当前的对象 TrackOpter.GET代表收集的意思
        // 其实意思就是effect里面访问 ref.value到这里就会收集
        track(this, TrackOpter.GET, 'value');
        
        // 返回的时候把被代理的这个值直接返回行了
        return this._value;
    }
    
    // newVal 新值
    set value(newVal:any){
        // 如何老值和新值相同直接返回
        if(!hasChange(newVal, this._value)) return;
        
        // 执行effect
        // TriggerOrTypes.SET 修改
        // TriggerOrTypes.ADD 添加
        // 如何被修改了就调用触发器来触发effect的执行
        trigger(this, TriggerOrTypes.SET, 'value', newVal, this._value);
        
        // 赋值的时候也需这么操作一下。
        // let refs=ref({a:1}) refs.value.a // {a:1} 被转成了代理对象。
        this._value = this.isShallow ? newVal : cover(newVal);
    }
}

分析一下toRef和toRefs的源代码

// 注意toRef返回的并不是 RefImpl 而是 ObjectRefImpl。

// 分析一下 ObjectRefImpl

/**
* toRef返回的类
* ObjectRefImpl
* 最后会转成defineProperty 【转成es5的形式class的这种形式变成了defineProperty】
* 这里为啥没有在包响应式的?
* 因为对象本身就是响应式的所以这里target也是响应式的,如果target不是响应式那么这里也就不是响应式的。
* 
* 举个例子
* let rfs = reactive({a:1})
* let b = toRef(rfs, 'a')
* b 就是访问'a'的代理
* b.value = 1; 实际上是refs['a'] = 1
* b.value 实际上是 refs['a'] 简化了调用写法而已。
* 是不是响应式的取决于
* rfs 它是就是他不是就不是。
* 这玩意有锤子用?
* // 这个很好理解 如果a变换了他会重新执行
* effect(()=>{app.innerHTML = rfs.a})
* setTimeout(()=>{rfs.a = 1 // 上面那个自然会更新}); // 触发了setter}, 3000)
*
* 如果这么来
* let {a} = refs;
* effect(()=>{
*    // // 为什么有问题?因为其一在外部用的 get 所以无法拿到effectActive
*    // 这个时候就无法响应式了
*    // app.innerHTMl = a; // 到这里了就只剩下一个值了。
*
* })
*
*
* // 如何实现上面这个需求?
* // let r = toRef(refs, 'a');
* // let {a} = refs; 这里的这个a放到括号里面
* effect(() => { app.innerHTML = r.value 完事儿});
* // toRef就为了干这个
* 在模版上.value就可以不要了直接r
* toRef只能转一个如果我有多个囊?toRefs吧
*/
class ObjectRefImpl {
    // 被代理的对象
    private _value:any = '';
    // 是否是ref
    public _is_Ref:boolean = true;
    // 默认吧target key 添加为公有属性
    constructor(public target:any, public key:any){

    }
    // 返回toRef(target, "key");
    // 把target对象的属性饭回来 这个会触发target的getter如何是代理对象的情况下会触发getter.
    get value() {
        return this.target[this.key];
    }

    // 直接把对象的值设置一下 
    // 同样的情况会直接触发了 目标对象的setter
    set value(val:any){
        this.target[this.key] = val;
    }
    
    // 可以看到这个对象并没有搞trigger和track都是如果源对象是响应式就走响应式那一套,如果没有拉倒了。
}




/**
 * 把一个对象中的属性变成ref
 * @param target 目标对象
 * @parm key 从对象里面拿出哪个key来
 * 
 * @description Ref 和 toRef是没有关系的是两个类
 * 
 */
function toRef(target:any, key:any) {
    // 这里vue返回的是ObjectRefImpl
    // 所以说不能返回的是RefImpl 两个不是一个类
    // return new RefImpl(key, target[key])

    return new ObjectRefImpl(target, key);

}



toRefs

/**
 * 
 * @param objects 目标对象
 * 
 * 这个玩意像
 * promisify    转一个
 * promisifyAll 转多个
 * 
 * toRefs怎用demo
 * let rs = reactive({a:1,b:2});
 * 
 * let alls = toRefs(res);
 * 
 * alls.a.value
 * alls.b.value
 * 
 * 
 * 可以这么来玩一下
 * 
 * let {a,b} = alls
 * 
 * a.value
 * b.value 了
 * 
 * 用toRefs就可以实现上面那个需求了
 * 
 * 有时候想转化一个就用toRef 多个就用 toRefs
 * 
 * 用到reactive 放到模版里面
 * 
 * let s = reactive({a:1});
 * let s1 = toRefs(s);
 * let {a}  = s1;
 * 
 * setup(){
 *  return {
 *  s
 *  }
 * }
 * 
 * 模版上用需要{{s.a}} 有点麻烦如果用toRefs
 * {{a}} 就可以直接用s1结构出来的那个 就可以把外成s给搞掉 模版就会直接用a.value 但是模版可以不加value了
 * 废了这么劲就是为了少写点。
 * 
 */
function toRefs(objects:any) {
    // objects 这个可能是个对象也可能是个数组
    let res = isArray(objects) ? new Array<any>(objects.length) : <any>{} ;
    for(let key in objects) {

        res[key] = toRef(objects, key);
    }
}

reactive 就是响应式api

ref 普通值用ref

结构响应式用 toRef 单个 toRefs多个

effect track trigger

**watchEffect -> 用到了 runtime-core -> watch api -> watch -> watch flush pre post **