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


    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)
    
  • 相关阅读:
    计算机图形学:光线追踪(ray tracing)
    快解析内网穿透助力外贸管理行业应对多种挑战
    291_C++_发送json数据给对于的URL【JSON数据交互】
    2、k8s 集群安装
    【牛客网-前端笔试题】——Javascript专项练习2
    Docker实战-部署GPE微服务的监控体系
    d3dx9_39.dll丢失怎么修复?d3dx9_39.dll丢失的四种修复办法分享
    GreenPlum DB向GBase迁移_TIMESTAMP类型
    VC++透明图片绘制的三种办法
    Matlab:奇异值
  • 原文地址:https://www.cnblogs.com/burc/p/17926729.html