前言

此前,咱们现已叙述过reactive函数了,现在让我来解说一下怎么自己打造一个与它功用相似的ref函数。首要咱们要知道,在 Vue 3 中,ref 是一个函数,用于创立一个呼应式目标,将普通的 JavaScript 数据转换为具有呼应式特性的数据。

ref.js

首要咱们就需求创立一个首要的js文件,以此完成 ref 函数的根底功用。

import { reactive } from './reactive.js'
import { track, trigger } from './effect.js'
export function ref(val) {  // 将原始类型数据变成呼应式 引证类型也能够
  return createRef(val)
}
function createRef(val) {
  // 判断val是否现已是呼应式
  if (val.__v_isRef) {
    return val
  }
  // 将val变为呼应式
  return new RefImpl(val)
}
 // const age = ref({n: 18})
class RefImpl {
  constructor(val) {
    this.__v_isRef = true // 给每一个被ref操作过的特点值都增加符号
    this._value = convert(val)
  }
  get value() {
    // 为this目标做依赖搜集
    track(this, 'value')
    return this._value
  }
  set value(newVal) {
    // console.log(newVal);
    if (newVal !== this._value) {
      this._value = convert(newVal)
      trigger(this, 'value') // 触发掉 'value' 上的一切副作用函数
    }
  }
}
function convert(val) {
  if (typeof val !== 'object' || val === null) {  // 不是目标
    return val
  } else {
    return reactive(val)
  }
}

以上代码就是一个简化版别的 ref 函数的完成,首要用于将原始类型数据或引证类型数据转换为具有呼应式特性的目标。让咱们逐步解说代码的各个部分:

  • ref 函数:

    • ref 函数承受一个参数 val,即要转换为呼应式目标的原始数据或引证类型数据。
    • 内部调用 createRef 函数来创立并回来一个呼应式目标。
  • createRef 函数:

    • createRef 函数承受一个参数 val,即要转换为呼应式目标的原始数据或引证类型数据。
    • 首要查看 val 是否现已是呼应式目标,假如是,则直接回来 val
    • 假如不是呼应式目标,则调用 RefImpl 构造函数来创立一个新的呼应式目标,并回来。
  • RefImpl 类:

    • RefImpl 类用于创立具有呼应式特性的目标。
    • 在构造函数中,为每个被 ref 操作过的特点值增加了 __v_isRef 符号,用于标识该特点现已被 ref 转换为呼应式目标。
    • 经过 _value 特点来存储值,并为其增加了 getter 和 setter 办法,用于获取和设置值。
    • 在 getter 办法中,经过 track 函数进行依赖搜集,保证在特点值发生变化时能够触发更新。
    • 在 setter 办法中,假如新值与旧值不同,则更新 _value,并经过 trigger 函数触发对应的副作用函数。
  • convert 函数:

    • convert 函数用于将原始数据转换为具有呼应式特性的目标。
    • 假如 val 是原始类型数据或 null,则直接回来 val
    • 假如 val 是目标类型,则调用 reactive 函数将其转换为呼应式目标。

effect.js

相同的,在ref函数中,咱们也需求进行副作用函数的搜集,这个功用的完成办法此前在(手动打造Vue中的reactive函数:探秘数据变化的魔法 – (juejin.cn))中咱们就现已解说过了,这里就不过多赘述了,直接贴代码。

const targetMap = new WeakMap();
let activeEffect = null; //得是一个副作用函数
export function effect(fn,options={}) { //watch,computed的核心逻辑
  const effectFn = () => {
    try {
      activeEffect = effectFn
      return fn()
    } finally {
      activeEffect = null
    }
  }
  if(!options.lazy){
    effectFn()
  }
  return effectFn
}
// 为某个特点增加effect
export function track(target,key) {
  // targetMap = { //存成这样的结构
  //   target: {
  //     key: [effect1,effect2,...]
  //   }
  // }
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    depsMap = new Map()
    targetMap.set(target, depsMap)
  }
  let dep = depsMap.get(key)
  if (!dep) { //改特点未增加过effect
    dep = new Set()
  }
  if (!dep.has(activeEffect) && activeEffect) {
    // 存入一个effect函数
    dep.add(activeEffect)
  }
  depsMap.set(key, dep)
}
// 触发特点effect
export function trigger(target,key) {
  const depsMap = targetMap.get(target)
  if(!depsMap){ //当前目标中一切的key都没有副作用函数,从来都没有被运用过
    return
  }
  const deps = depsMap.get(key)
  if (!deps) { //这个特点没有依赖
    return
  }
  deps.forEach(effectFn => {
    effectFn() //将该特点上的一切副作用函数悉数触发
  });
}

需求了解的朋友能够去了解一下之前的文章。

效果测试

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script type="module">
        import {ref} from './ref.js'
        import {effect} from './effect.js'
        const age = ref(18)
        age.value = 19
        effect(() => {
            console.log(age.value);
        })
        setInterval(() => {
            age.value = age.value   1
        }, 1000)
    </script>
</body>
</html>

在以上代码中,咱们经过导入自己创立的 ref 函数和 effect 函数,演示了怎么创立呼应式数据,并且经过副作用函数监听数据的变化。

探究Vue 3 中的ref函数:从原始值到呼应式目标的改变

结语

至此,咱们现已简略的完成了ref函数和reactive函数。在此过程中咱们能够发现,ref 函数和 reactive 函数尽管都用于创立呼应式数据,但它们有一些关键的差异:

  1. 回来值类型:

    • ref 函数回来的是一个带有 .value 特点的包装目标,而这个 .value 特点才是呼应式的。因而,经过 ref 创立的数据需求经过 .value 特点来访问和修正。
    • reactive 函数回来的是一个普通的 JavaScript 目标,但目标内部的一切特点都会被递归地转换成呼应式的。
  2. 运用场景:

    • ref 首要用于包装根本类型数据或单个目标,例如数字、字符串、布尔值等,以及一些需求被直接访问和修正的数据。
    • reactive 更适合用于创立包括多个特点的杂乱目标,例如包括多个特点的目标或嵌套目标,这样能够一次性地将整个目标及其特点都转换为呼应式的。
  3. 访问和修正方式:

    • 运用 ref 创立的数据,需求经过 .value 特点来访问和修正数据的值。
    • 运用 reactive 创立的数据,能够直接访问和修正目标的特点,不需求额定的 .value 特点。
  4. 功能影响:

    • 由于 ref 创立的是一个简略的包装目标,它本身的引证不会发生变化,因而在比较引证时能够运用 === 运算符进行快速的比较。
    • reactive 创立的是一个署理目标,内部存在多个引证,因而比较引证时需求逐层比较目标内部的特点,功能或许略逊于 ref

而为什么现在都是引荐运用ref而不是reactive呢?这是由于:

1、reactive有一些局限性:

  • 有限的值类型:它只能用于目标类型 (目标、数组和如MapSet这样的集合类型。它不能持有如stringnumberboolean这样的原始类型。
  • 不能替换整个目标:由于 Vue 的呼应式跟踪是经过特点访问完成的,因而咱们必须始终保持对呼应式目标的相同引证。这意味着咱们不能轻易地“替换”呼应式目标,由于这样的话与第一个引证的呼应性衔接将丢失。
  • 对解构操作不友好:当咱们将呼应式目标的原始类型特点解构为本地变量时,或许将该特点传递给函数时,咱们将丢失呼应性衔接。

2、首要原因

  1. 更符合单一责任准则:

    • ref 首要用于包装根本类型数据或单个目标,它的责任更加单一明确,只担任包装数据并提供 .value 特点来访问和修正数据。而 reactive 则适用于创立包括多个特点的杂乱目标,它需求担任递归地将目标及其特点转换为呼应式的,这样或许会与单一责任准则不行符合。
  2. 更简单进行类型揣度:

    • 运用 ref 创立的数据是一个带有 .value 特点的包装目标,这样在 TypeScript 中能够更简单进行类型揣度,使得代码更加具有类型安全性。相比之下,直接运用 reactive 创立的数据目标或许会在类型揣度上稍显杂乱。
  3. 更好的功能表现:

    • 由于 ref 创立的数据是一个简略的包装目标,它本身的引证不会发生变化,因而在比较引证时能够运用 === 运算符进行快速的比较,这样能够提升一定的功能。相比之下,reactive 创立的是一个署理目标,内部存在多个引证,比较引证时需求逐层比较目标内部的特点,功能或许略逊于 ref

综上所述,尽管 refreactive 在功用上都能完成呼应式数据的创立,可是引荐运用 ref 首要是由于它在语法上更加简洁明了,符合单一责任准则,更简单进行类型揣度,同时也具有更好的功能表现。