前言
前面我们学习了如何实现一个简单的 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)
在 obj 中 value 虽然以函数的方式写入,我们可以使用属性的方式进行数据获取
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 逻辑。而基本类型数据的取值和更新会触发 get 和 set 。两者区别在于 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)
最后
到这里就已经完全实现了我们的 ref 响应式,代码在 gitee.com/Provens/vue…
02-ref 分支中,有兴趣的小伙伴可以 clone 下来一起学习!下篇文章我们继续学习 watch 与 computed