【源码系列#05】Vue3响应式原理(Ref)

  • 【源码系列#05】Vue3响应式原理(Ref)已关闭评论
  • 26 次浏览
  • A+
所属分类:Web前端
摘要

ref:接受一个参数值并返回一个响应式且可改变的 ref 对象。ref 对象拥有一个指向内部值的单一属性 .value


Ref & ShallowRef

ref:接受一个参数值并返回一个响应式且可改变的 ref 对象。ref 对象拥有一个指向内部值的单一属性 .value

可以将 ref 看成 reactive 的一个变形版本,这是由于 reactive 内部采用 Proxy 来实现,而 Proxy 只接受对象作为入参,这才有了 ref 来解决值类型的数据响应,如果传入 ref 的是一个对象,内部也会调用 reactive 方法进行深层响应转换

const count = ref(0) console.log(count.value) // 0  count.value++ console.log(count.value) // 1 

shallowRef:ref() 的浅层作用形式。和 ref() 不同,浅层 ref 的内部值将会原样存储和暴露,并且不会被深层递归地转为响应式。只有对 .value 的访问是响应式的。

const state = shallowRef({ count: 1 })  // 不会触发更改 state.value.count = 2  // 会触发更改 state.value = { count: 2 } 

源码实现

  • @issue1 如果是对象和数组,则调用 reactive方法 转化为响应式对象(ref会转换,shallowRef不会转换)
  • @issue2 getter 取值的时候收集依赖
  • @issue3 setter 设置值的时候触发依赖
/**  * @desc 如果是对象和数组,则转化为响应式对象  */ function toReactive(value) {   return isObject(value) ? reactive(value) : value }  /**  * @desc RefImpl  * @issue1 如果是对象和数组,则转化为响应式对象  */ class RefImpl {   // ref标识   public __v_isRef = true   // 存储effect   public dep = new Set()   public _value   constructor(public rawValue, public _shallow) {     // @issue1     this._value = _shallow ? rawValue : toReactive(rawValue)   }   get value() {     // 取值的时候收集依赖     trackEffects(this.dep)     return this._value   }   set value(newValue) {     // 新旧值不相等     if (newValue !== this.rawValue) {       // @issue1       this._value = this._shallow ? newValue : toReactive(newValue)       this.rawValue = newValue       // 设置值的时候触发依赖       triggerEffects(this.dep)     }   } }   // 接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的属性 .value。 export function ref(value) {   return new RefImpl(value) }  

测试代码

/**  * 1. Ref  **/ const person = ref({   name: '柏成',   age: 25, }) effect(() => {   app.innerHTML = person.value.name })  setTimeout(() => {   person.value.name = '柏成2号' // 会触发更改 }, 1000)  /**  * 2. shallowRef  */ const person = shallowRef({   name: '柏成',   age: 25, }) effect(() => {   app.innerHTML = person.value.name })  setTimeout(() => {   person.value.name = '柏成2号' // 不会触发更改 }, 1000)  setTimeout(() => {   person.value = {     name: '柏成9号' // 会触发更改   } }, 2000) 

toRef & toRefs

toRef:基于响应式对象上的一个属性,创建一个对应的 ref。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然。

const state = reactive({   foo: 1,   bar: 2 })  const fooRef = toRef(state, 'foo')  // 更改该 ref 会更新源属性 fooRef.value++ console.log(state.foo) // 2  // 更改源属性也会更新该 ref state.foo++ console.log(fooRef.value) // 3 

toRefs:将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 toRef() 创建的。

const state = reactive({   foo: 1,   bar: 2 })  const stateAsRefs = toRefs(state)  // 这个 ref 和源属性已经 “链接上了” state.foo++ console.log(stateAsRefs.foo.value) // 2  stateAsRefs.foo.value++ console.log(state.foo) // 3 

源码实现

class ObjectRefImpl {   // 只是将.value属性代理到原始类型上   constructor(public object, public key) {}      get value() {     return this.object[this.key]   }      set value(newValue) {     this.object[this.key] = newValue   } }  // 基于响应式对象上的一个属性,创建一个对应的 ref。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然。 export function toRef(object, key) {   return new ObjectRefImpl(object, key) }  // 将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 toRef() 创建的。 export function toRefs(object) {   const result = isArray(object) ? new Array(object.length) : {}    for (let key in object) {     result[key] = toRef(object, key)   }    return result } 

测试代码

// 对象 const person = reactive({   name: '柏成',   age: 18 }) // 数组 const numbers = reactive([1, 2, 3, 4, 5])  // 注意!直接解构后会丢失响应性的特点!!! // let { name, age } = person  const {   name,   age } = toRefs(person) const [first] = toRefs(numbers)  effect(() => {   app.innerHTML = `${name.value},${age.value}岁。第一个数字为${first.value}。` })  setTimeout(() => {   name.value = '柏成9号'   first.value = 999 }, 1000) 

自动脱ref

在js中访问ref时需要.value获取,但是在模版中却可以直接取值,不需要加.value!这里就用到了 proxyRefs 自动脱ref方法

源码实现

export function proxyRefs(object) {   return new Proxy(object, {     // 代理的思想,如果是ref 则取ref.value     get(target, key, recevier) {       let r = Reflect.get(target, key, recevier)       return r.__v_isRef ? r.value : r     },     // 设置的时候如果是ref,则给ref.value赋值     set(target, key, value, recevier) {       let oldValue = target[key]       if (oldValue.__v_isRef) {         oldValue.value = value         return true       } else {         return Reflect.set(target, key, value, recevier)       }     },   }) } 

测试代码

const name = ref('柏成') const age = ref('24')  const person = proxyRefs({   name,   age,   sex: '男' })  effect(() => {   app.innerHTML = `${person.name},${person.age}岁。性别${person.sex}。` })  setTimeout(() => {   name.value = '柏成9号' }, 1000)