前言
此前,咱们现已叙述过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
函数,演示了怎么创立呼应式数据,并且经过副作用函数监听数据的变化。
结语
至此,咱们现已简略的完成了ref
函数和reactive
函数。在此过程中咱们能够发现,ref
函数和 reactive
函数尽管都用于创立呼应式数据,但它们有一些关键的差异:
-
回来值类型:
-
ref
函数回来的是一个带有.value
特点的包装目标,而这个.value
特点才是呼应式的。因而,经过ref
创立的数据需求经过.value
特点来访问和修正。 -
reactive
函数回来的是一个普通的 JavaScript 目标,但目标内部的一切特点都会被递归地转换成呼应式的。
-
-
运用场景:
-
ref
首要用于包装根本类型数据或单个目标,例如数字、字符串、布尔值等,以及一些需求被直接访问和修正的数据。 -
reactive
更适合用于创立包括多个特点的杂乱目标,例如包括多个特点的目标或嵌套目标,这样能够一次性地将整个目标及其特点都转换为呼应式的。
-
-
访问和修正方式:
- 运用
ref
创立的数据,需求经过.value
特点来访问和修正数据的值。 - 运用
reactive
创立的数据,能够直接访问和修正目标的特点,不需求额定的.value
特点。
- 运用
-
功能影响:
- 由于
ref
创立的是一个简略的包装目标,它本身的引证不会发生变化,因而在比较引证时能够运用===
运算符进行快速的比较。 -
reactive
创立的是一个署理目标,内部存在多个引证,因而比较引证时需求逐层比较目标内部的特点,功能或许略逊于ref
。
- 由于
而为什么现在都是引荐运用ref
而不是reactive
呢?这是由于:
1、reactive
有一些局限性:
-
有限的值类型:它只能用于目标类型 (目标、数组和如
Map
、Set
这样的集合类型。它不能持有如string
、number
或boolean
这样的原始类型。 - 不能替换整个目标:由于 Vue 的呼应式跟踪是经过特点访问完成的,因而咱们必须始终保持对呼应式目标的相同引证。这意味着咱们不能轻易地“替换”呼应式目标,由于这样的话与第一个引证的呼应性衔接将丢失。
- 对解构操作不友好:当咱们将呼应式目标的原始类型特点解构为本地变量时,或许将该特点传递给函数时,咱们将丢失呼应性衔接。
2、首要原因
-
更符合单一责任准则:
-
ref
首要用于包装根本类型数据或单个目标,它的责任更加单一明确,只担任包装数据并提供.value
特点来访问和修正数据。而reactive
则适用于创立包括多个特点的杂乱目标,它需求担任递归地将目标及其特点转换为呼应式的,这样或许会与单一责任准则不行符合。
-
-
更简单进行类型揣度:
- 运用
ref
创立的数据是一个带有.value
特点的包装目标,这样在 TypeScript 中能够更简单进行类型揣度,使得代码更加具有类型安全性。相比之下,直接运用reactive
创立的数据目标或许会在类型揣度上稍显杂乱。
- 运用
-
更好的功能表现:
- 由于
ref
创立的数据是一个简略的包装目标,它本身的引证不会发生变化,因而在比较引证时能够运用===
运算符进行快速的比较,这样能够提升一定的功能。相比之下,reactive
创立的是一个署理目标,内部存在多个引证,比较引证时需求逐层比较目标内部的特点,功能或许略逊于ref
。
- 由于
综上所述,尽管 ref
和 reactive
在功用上都能完成呼应式数据的创立,可是引荐运用 ref
首要是由于它在语法上更加简洁明了,符合单一责任准则,更简单进行类型揣度,同时也具有更好的功能表现。