vue.js 设计与实现-vue3源码学习(5)

358 阅读3分钟

前言

前面我们学习了如何实现一个简单的 reactive effect 函数以及打包测试。本篇我们学习 ref 函数

vue3源码学习系列 (1) juejin.cn/post/722481…
vue3源码学习系列 (2) juejin.cn/post/723229…
vue3源码学习系列 (3) juejin.cn/post/723596…
vue3源码学习系列 (4) juejin.cn/post/723814…

前置

vue3 中我们是使用 .value 方式来获取 ref 响应式数据的,为什么要以 .value 方式呢? 我们来看一下代码,就明白了。

let obj={
         name:'张三',
         fun:function(){
              console.log(this.name)
         },
         get value (){
            return '我是张三'
         }
   }
 console.log(obj)

objvalue 虽然以函数的方式写入,我们可以使用属性的方式进行数据获取 image.png

ref

创建 ref 函数

export interface Ref<T=any>{
    value:T
}
export function ref(value:unkonw){
  return createRef(value,false)
}
 function createRef(rawValue:unknown,shallow:boolean){
 if(isRef(rawValue)){
  return rawValue //如果是ref数据,就直接返回无需再处理
 }
 return new RefImpl(rawValue,shallow) //如果不是,则需要生成 ref 数据
}
export  function isRef(r:any):r is Ref {
    return !!(r&&r.__v_isRef==true)  //我们会在 RefImpl 类中定义 __v_isRef 有则表示已经生成实例,是ref数据
}

RefImpl

import { activeEffect, trackEffect, triggerEffects } from './effect';
import { toReactive } from './reactive';
import { Dep, createDep } from './dep';
import { hasChanged } from '@vue/shared';
class RefImpl<T>{
    private _value:T //
    public dep?:Dep=undefined //收集依赖 存放 fn 函数
    public readonly __v_isRef=true //实例化后根据此属性判断传入的数据是否为 ref 类型
    constructor(value:T,public readonly __v_isShallow:Boolean){
      this._value=__v_isShallow?value:toReactive(value)
      // toReactive 函数,这里传值可能为对象,也可能为基本数据类型,所以我们需要区分处理
    }
    get value(){
        trackRefValue(this) //收集依赖
        return this._value
    }
    set value(newValue){}
}
//收集依赖
export function trackRefValue(ref){
  if(activeEffect){
    trackEffect(ref.dep||(ref.dep=createDep()))  
    //trackEffect函数我们在reactive 函数中已经实现 ,主要作用是用来更新 fn 函数
  }
}

toReactive 函数

//在 src/reactive.ts 中
// toReactive 函数很简单,如果是对象我们生成 proxy 实例进行返回,如果是基本数据类型,直接返回,不做处理
export const toReactive=<T extends unknown>(value:T):T=>
isObject(value)?reactive(value as object) :value

// shared/src/index.ts 
export const isObject=(value:unknown)=>value!==null&&typeof value==='object' 

此时的我们已经完成了引用类型数据的 ref 响应式代码,基本类型数据修改时还无法触发更新!在 ref 响应式中标,引用类型数据的取值和更新只会触发 RefImpl 中 的 get ,不会去走 set 逻辑。而基本类型数据的取值和更新会触发 getset 。两者区别在于 set 中的触发

基本类型数据的更新逻辑

我们来继续看 RefImpl 中的代码

class RefImpl<T>{
    private _value:T
    private rawValue:T  //增加初始值
    public dep?:Dep=undefined
    public readonly __v_isRef=true
    constructor(value:T,public readonly __v_isShallow:Boolean){
      this.rawValue=value
      this._value=__v_isShallow?value:toReactive(value)
    }
    get value(){
        trackRefValue(this)
        return this._value
    }
    set value(newValue){
    //基本数据类型在更新时会触发该函数,进行新旧数据对比然后,如果数据有变化则进行依赖触发
       if(hasChanged(newValue,this.rawValue)){
            this.rawValue=newValue
            this._value=toReactive(newValue)
            tigggerRefValue(this)
       }
    }
}
//与 reactive 中 set 一样进行依赖触发
export function tigggerRefValue(ref){
  if(ref.dep){
    triggerEffects(ref.dep)
  }
}
// shared/src/index.ts 
export const hasChanged=(newValue:unknown,value:unknown)=>!Object.is(newValue,value)

image.png

image.png

最后

到这里就已经完全实现了我们的 ref 响应式,代码在 gitee.com/Provens/vue…
02-ref 分支中,有兴趣的小伙伴可以 clone 下来一起学习!下篇文章我们继续学习 watchcomputed