vue3源码 - ref篇

836 阅读3分钟

前言

ref相信大家在使用vue3的时候会用的很多, 因为这个是定义基本类型响应式的一个方法,那么你对ref的了解又有多少呢?如果你想了解ref底层, 此文章将帮助你了解它的底层

问题

首先,想问一下大家2个问题

  • refreactive有什么区别?
  • ref中可以放对象作为参数吗? reactive可以放基础值作为参数吗? 答案将在文末展示

ref实现

本次实现通过例子 + vue3源码 逐步实现

例子

<!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 id="app"></div>
  <script src="../vue/dist/vue.global.js"></script>
  <script>
    let { ref, effect }  = Vue
    let name= ref('vvv')
    effect(() => {
      app.innerHTML =  name.value
    })
    console.log(name);
    setTimeout(() => {
      name.value = 'vvv2'
    }, 1000)
  </script>
</body>
</html>

打开浏览器

ref.gif 从上面的例子中可以看到ref后,返回的是一个RefImpl实例, 并且具有 __v_isRef __v_isShallow _rawValue_value等等的一系列属性

大概了解下ref, 下面就让我们来去实现一下ref
ref有俩种形式, 分别是refshallowRef

export const ref = (value) => {}
export const shallowRef = (value) => {}

上面的参数value 就是我们传进入的值啦
为了不重复代码,我们用用一个函数createRef, 通过不同参数来实现俩种不一样的ref

function createRef (rawValue, shallow = false) {
}

我们上面的代码变化一下, 就变成这样子了

export const ref = (value) => {
  return createRef(value)
}
export const shallowRef = (value) => {
  return createRef(value, true)
}

我们下面只需要实现createRef就可以实现我们的ref

function createRef (rawValue, shallow = false) {
  if (isRef(rawValue)) {
    return rawValue
  }
  return new RefImpl(rawValue, shallow)
}

isRef函数是判断这个value是否已经ref
具体的实现方式是:

export function isRef(r) { // 判断是否已经ref过了
  return !!(r && r.__v_isRef === true)
}

createRef中的代码, 为什么会还有new RefImpl()呢? 可以在看看上面的动图, 上面的动图是通过vue3源码举的例子,通过ref后会返回一个 RefImpl实例
那么接下来只需要去实现RefImpl实例即可

const convert = (val) => isObject(val) ? reactive(val): val

class RefImpl { // 创建refImpl类
  public _value 
  public _rawValue  
  public readonly __v_isRef = true // ref标识
  constructor(value, public readonly __v_isShallow) {
    this._value = value // 获取到旧值
    this._rawValue = value
  }
  get value() { // track(依赖收集)
    track(this, trackOpTypes.GET, 'value')
    return this._value
  }
  set value(newValue) { // trigger(触发依赖更新)
    if(hasChange(newValue, this._rawValue)) {
       this._rawValue = newValue
       this._value = this.__v_isShallow ? newValue : convert(newValue)
       trigger(this, TriggerOrTypes.SET, 'value', newValue)
    }
  }
}

上面的tracktrigger 代码是依赖收集和依赖触发(更新), 具体的详细过程可以看vue3源码 - effect依赖收集触发更新篇, 还有一些utils函数 可以从这里看到喔

此外, ref对象对象的处理是用过了reactive去处理
偷偷告诉你哟: 上面这个实现ref的代码是通过Object.defineProperty去实例的

测试

新建一个ref.html文件

<!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 id="app"></div>
  <script src="../node_modules/@vue/reactivity/dist/reactivity.global.js"></script>
  <script>
    let { ref, effect }  = VueReactivity
    let name = ref('vvv')
    console.log(name);
    effect(() => {
      app.innerHTML = name.value
    })
    setTimeout(() => {
      name.value = 'vvv2'
    }, 1000)
  </script>
</body>
    </html>

打开浏览器, 看看我们实现的怎么样

ref2.gif 为了具有辨识度, 我在RefImpl添加了前缀My
可以看到我们的ref已经实现了, vue3源码其他相关的实现静待下一篇哟

文末

ref的实现已经完毕, 接下来回答一下上面的俩个问题

  • refreactive有什么区别?
    通过源码我们可以看到ref是通过Object.defineProperty实现的, 而reactive是通过proxy实现的, 这时候就有同学问? 为什么ref不通过proxy实现呢, 主要是因为proxy只支持对象, 爱莫能助ya.

  • ref中可以放对象作为参数吗? reactive可以放基础值作为参数吗?
    ref是可以传对象的, 如果传的是对象会给reactive去实现
    reactive放基础值是没什么作用的, 具体为什么可以看vue3源码 - 响应式数据reactive篇

最后

如果觉得本文对你有帮助,记得点赞👍🏻 、 收藏⭐️ 加关注➕哟

vue3源码实现系列

vue3源码 - 响应式数据reactive篇

vue3源码 - effect依赖收集触发更新篇

vue3源码 - ref篇

vue3源码 - toRef和toRefs篇