侧边栏壁纸
博主头像
MicroMatrix博主等级

曲则全,枉则直,洼则盈,敝则新,少则得,多则惑。是以圣人抱一为天下式。不自见,故明;不自是,故彰;不自伐,故有功;不自矜,故长。夫唯不争,故天下莫能与之争。古之所谓“曲则全”者,岂虚言哉!诚全而归之。

  • 累计撰写 80 篇文章
  • 累计创建 21 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

vue3源码学习-9-ref的实现

蜗牛
2022-06-21 / 0 评论 / 0 点赞 / 8 阅读 / 6470 字 / 正在检测是否收录...

摘要

在页面编写中,之前都是使用reactive来包裹对象,这样对象属性的值改变,其对应的effect包裹渲染动作就会被触发。并且通常有函数解构的操作,例如let people = reactive({name:"张三",age:24}); let {name,age} = people;。如果这么结构操作的话,name和age就会变成普通的变量,那么如果在effect中使用的话,就算改变了值也不会触发回调函数。那么为了解决例如此类的需求,vue3提供了ref、toRef、toRefs

官方中的写法探究

引入官方的包,尝试了几种简单的写法。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div class="" id="app"></div>
  <script src="./dist/reactivity.global.js"></script>
  <script>
    const { reactive, effect,toRef,ref,toRefs,proxyRefs } = VueReactivity
    // let people = reactive({name:"张三",age:99})
   let name = ref("张三")
   let age = ref(23)
   let people = proxyRefs({name,age})
  // let {name,age} = toRefs(people)
    effect(() => {
      app.innerHTML = `${people.name}今年${people.age}岁`
    })

    setTimeout(() => {
      people.name = "利斯"
    },1000)
  </script>
</body>
</html>

可以用Ref包裹基础数据类型,也可以包裹引用型数据。让数据变成响应式数据,可以被effect收集,然后更新数据触发回调函数。

编写Ref

首先开始编写ref.ts,ref会被传入基础数据类型和引用型数据。所以方法要判断一下数据类型。

import { isArray, isObject } from '@vue/shared'
import { trackEffect, triggerEffect } from './effect'
import { reactive } from './reactive'
function toReactive(value){
    return isObject(value) ? reactive(value) : value
}

class RefImpl{
    public _value
    constructor(public rawValue){
        this._value = toReactive(rawValue)
    }

    get value(){
        return this._value;
    }

    set value(newValue){
        if(newValue !== this.rawValue){
            this._value = toReactive(newValue);
            this.rawValue = newValue;
        }
    }
}

function ref(value){
    return new RefImpl(value)
}

上面是一个初步的对象包裹,但是还没加入依赖收集。现在加入源码学习-7中分离的依赖收集函数和更新触发的函数。

......
import { isArray, isObject } from '@vue/shared'
import { trackEffect, triggerEffect } from './effect'
import { reactive } from './reactive'
function toReactive(value){
    return isObject(value) ? reactive(value) : value
}

class RefImpl{
    public _value
    public dep = new Set()
    public __v_isRef = true
    constructor(public rawValue){
        this._value = toReactive(rawValue)
    }

    get value(){
        trackEffect(this.dep)
        return this._value;
    }

    set value(newValue){
        if(newValue !== this.rawValue){
            this._value = toReactive(newValue);
            this.rawValue = newValue;
            triggetEffect(this.dep)
        }
    }
}

function ref(value){
    return new RefImpl(value)
}

这样就完成了Ref的基本功能。现在有了包裹对象,那么就有第二中需求,结构这种需求。toRefs和toRef就是vue3提供的函数。

......
class ObjectRefImpl {
  constructor(public object, public key) {}

  get value() {
    return this.object[this.key]
  }

  set value(newValue) {
    this.object[this.key] = newValue
  }
}

export function toRef(object, key) {
  return new ObjectRefImpl(object, key)
}

export function toRefs(object) {
  const result = isArray(object) ? new Array(object.length) : {}

  for (let key in object) {
    result[key] = toRef(object, key)
  }

  return result
}

toRefs就比较简单就是单个toRef的单个版本实现,所以基于toRefs就基于toRef实现就行。
另外可以观察到vue3中Ref包裹的对象在template中使用发现不需要xx.value,这是vue3官方做的一个优化,实际就像是ref包裹的反向过程。

编写proxyRefs

上面知道了vue3提供了一个proxyRefs这样的函数,来使得ref对象使用更加方便了。

......
export function proxyRefs(object) {
  return new Proxy(object, {
    get(target, key, recevier) {
      let r = Reflect.get(target, key, recevier)
      return r.__v_isRef ? r.value : r
    },

    set(target, key, value, receiver) {
      let oldValue = target[key]
      if (oldValue.__v_isRef) {
        oldValue.value = value
        return true
      } else {
        return Reflect.set(target, key, value, receiver)
      }
    },
  })
}

template模板上通常都是get数据那么就是代理对象反射这个.value这样,为啥要用Reflect来反射,是因为this的指向问题。第三篇文章分析了这个问题。

git:[@github/MicroMatrixOrg/vue3-plan/tree/ref]
0

评论区