前言
在vue3
中,ref
和reactive
是我们最常用的两个方法,它们的作用都是为了实现将一个普通的变量变为响应式变量,只是ref
可以将对象和基本类型都变为响应式,而reactive
只能将对象变为响应式。
reactive
reactive是通过使用proxy去代理一个对象,从而监听这个对象的十三中行为get, set, has, deleteProperty, apply, construct, ownKeys, getOwnPropertyDescriptor, defineProperty, isExtensible, preventExtensions, getPrototypeOf, setPrototypeOf
,然后去收集它的副作用函数,并将它们都执行掉,从而实现这个将传入对象变为响应式
js
import { mutableHandlers } from './baseHandles.js'
const reactiveMap = new WeakMap()
export function reactive(target) {
return createReactiveObject(target, mutableHandlers, reactiveMap)
}
function createReactiveObject(target, proxyHandlers, proxyMap) {
if (typeof target !== 'object') {
return target
}
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
const proxy = new Proxy(target, proxyHandlers)
proxyMap.set(target, proxy)
return proxy
}
首先我们要抛出一个reactive
方法,并且接收一个参数,接收的这个参数就是传入的普通变量,我们需要将其变为响应式变量,所以我们要创建一个createReactiveObject
的方法,将传入的对象进行代理。
不过我们要首先判断传入的参数是否为一个对象和这个对象是否已经被代理了,所以我们创建了一个reactiveMap
的实例对象,将每次被代理的对象传入,使用proxyMap.set(target, proxy)
方法往对象里面存值,使用proxyMap.get(target)
判断对象里面是否有这个值,有的话说明它已经是代理对象了。 new Proxy(target, proxyHandlers)
后面的proxyHandlers
是我们代理对象后,对象发生读取或修改属性等行为时,我们进行拦截读取操作。
所以我们要打造一个mutableHandlers
函数去监听和拦截目标对象的多种操作行为。
js
import { isObject } from "../shared/index.js";
import { reactive } from "./reactive.js";
import { track, trigger } from "./effect.js";
const get = createGetter()
const set = createSetter()
function createGetter() {
return function(target, key, receiver) {
const res = Reflect.get(target, key, receiver)
// 在值初次被读取时就要进行依赖收集
track(target, 'get', key)
if (isObject(res)) { // 子对象递归
return reactive(res)
}
return res
}
}
function createSetter() {
return function(target, key, value, receiver) {
const res = Reflect.set(target, key, value, receiver)
// 触发依赖
trigger(target, 'set', key)
return res
}
}
export const mutableHandlers = {
get,
set
}
由于写多了可能更难以理解,这里我们就演示代理对象get和set的两种行为,Reflect.get(target, key, receiver)
用来获得被读取的对象key
对应的值,并且在读取值的时候我们要对这个对象的副作用函数进行收集,并且读取的key如果是一个对象我们要进行递归操作,将这个key也变为响应式对象
Reflect.set(target, key, value, receiver)
是获取被修改的key的值,value是要赋给属性的新值。然后对依赖这个对象被修改的key的函数进行触发。
js
const targetMap = new WeakMap()
let activeEffect = null // 副作用函数
export function effect(fn, options = {}) {
const effectFn = () => {
try {
activeEffect = fn
return fn()
} catch (error) {
activeEffect = null
}
}
effectFn()
}
// 依赖收集
export function track(target, type, key) {
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let deps = depsMap.get(key)
if (!deps) {
deps = new Set()
}
if (!deps.has(activeEffect) && activeEffect) {
deps.add(activeEffect)
}
depsMap.set(key, deps)
}
// 触发依赖
export function trigger(target, type, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const deps = depsMap.get(key)
if (!deps) return
deps.forEach(effect => {
effect()
})
}
因为要做函数的依赖收集和触发,所以我们来打造这两个方法,我们创建一个targetMap
对象用来收集传入的对象,防止重复进行对象的依赖收集,depsMap
也是一个对象,用来收集副作用函数。
effect
函数是用来被当成副作用函数收集触发的演示,最后将被收集的副作用函数全部放入deps这个set的数据结构中去,将deps内的副作用函数全部触发。
js
<template>
<div @click="add">
<p>count:{{state.count}}</p>
</div>
</template>
<script setup>
import { reactive } from './reactivity/reactive.js'
import { effect } from './reactivity/effect.js'
const state = reactive({
count: 10,
like: {
a: 2
}
})
effect(() => {
console.log('effect',state.count)
})
const add = () => {
state.count++
console.log(state);
console.log(state.count);
}
</script>
<style lang="css" scoped>
</style>
我们给使用自己打造的reactive去将state对象变为响应式,然后看看,我们使用依赖收集的effect函数是否会重新执行,再打印我们使用reactive代理后的对象是什么样的
可以看到每次对象中的count属性发生变化,我们收集的依赖函数effect都会重新执行,且state对象已经不是原来的state对象了,而是一个Proxy代理之后的对象。
ref
我们知道ref是可以代理对象和原始类型的,那他又是怎么实现的呢
js
import { reactive } from "./reactive.js";
import { isObject } from "../shared/index.js";
import { track, trigger } from "./effect.js";
export function ref(val) {
return new RefImpl(val)
}
class RefImpl {
constructor(val) {
this._val = convert(val)
}
get value() {
track(this, 'get', 'value')
return this._val
}
set value(newVal) {
if (newVal !== this._val) {
this._val = newVal
trigger(this, 'set', 'value')
return this._val
}
}
}
function convert(val) {
return isObject(val) ? reactive(val) : val
}
这里直接使用class创建一个类入ref的值作为参数,去返回一个RefImpl的实例对象,在RefImpl类中,我们先判断传入参数是否为对象,如果是对象就用reactive处理,将实例对象中得_val属性变为响应式对象,如果是原始值,则直接存储在对象_val属性上。
外部访问value属性时,我们要做副作用函数收集,并将要我们创建出来得实例对象得_val属性返回。如果外部要修改值,我们要使用newVal去代替原来实例对象上_val的属性,然后将副作用函数全部触发,并将修改后的值返回。
js
<template>
<div @click="add">
<p>age:{{age}}</p>
</div>
</template>
<script setup>
import { reactive } from './reactivity/reactive.js'
import { effect } from './reactivity/effect.js'
import { ref } from './reactivity/ref.js'
const age = ref(10)
effect(() => {
console.log('effect', age.value)
})
const add = () => {
age.value++
console.log(age);
}
</script>
<style lang="css" scoped>
</style>
我们使用直接打造的ref去将age变为响应式变量,effect作为依赖函数被触发
可以看到依赖函数在age每次改变都会进行触发,并且使用ref将age变为了一个RefInpl,_val才是age的值。
总结
reactive:
reactive
的实现原理是通过 JavaScript 的 Proxy
对象拦截对目标对象的操作(如读取、赋值等)。在读取属性时,调用 track
收集依赖;在修改属性时,调用 trigger
触发更新。内部使用 WeakMap
存储依赖关系,确保对象被垃圾回收时释放内存,从而实现高效、精细的响应式系统。
ref:
ref
的实现原理是将值包装进一个包含 get
和 set
访问器的类(如 RefImpl
)中,通过访问 .value
来触发依赖的收集(track
)和更新(trigger
)。对于对象值,则递归调用 reactive
转换为响应式对象。这样,无论是基本类型还是对象,都能实现响应式数据绑定,支持自动追踪变化并通知相关联的副作用函数更新。