关于reactive
最近小编在刷vue面经时,有看到ref与reacive的区别,关于ref大家都耳熟能详,但是对于ractive,小编一开始只了解到与ref的区别是不需要.value访问值并且只能绑定引用数据类型,而ref既能绑定基本类型又能绑定引用数据类型,那么reactive的存在的意义在哪里?
这样的困惑驱使小编不能仅满足于表面的理解。在 Vue 庞大而精密的体系里,reactive 必然承载着特殊的使命。通过查阅资料和源码,小编这了解到reactive背后的奥秘,下面就一一道来。
github.com/vuejs/core/... 附上github上reactive的源码
reactive的设计理念
首先,为什么reactive只能接受引用数据类型?这是因为reactive是基于ES6的Proxy实现的响应式,而Proxy只能代理引用数据类型。
而ref在绑定基本数据类型时是基于Object.defineProperty 通过数据劫持变化来实现数据响应式的。对于引用数据类型,defineProperty的方法存在一些弊端:比如无法监听到对象属性的新增和删除,也无法监听数组索引的直接设置和length变化。这里简单对比一下vue响应式方式。
| 实现方式 | 适用类型 | 核心缺陷 |
|---|---|---|
| Object.defineProperty(ref 底层) | 基本类型(包装为对象) | 1. 无法监听对象新增 / 删除属性;2. 无法监听数组索引 / 长度变化;3. 只能劫持单个属性 |
| Proxy(reactive 底层) | 引用类型(对象 / 数组 / Map 等) | 无上述缺陷,可代理整个对象,支持动态增删属性、数组操作 |
因此,ref在代理对象时也是借助到reactive
reactive基于Proxy的响应式系统能完美解决这些问题,下面我们来写一个简单的reactive响应式
reactive代理对象
要实现reactive实现数据响应式,我们需要先创建一个reactive方法,通过Proxy进行代理。 其中,proxy代理的target需要是对象并且没有被代理过。
js
//创建一个Map来保存代理过的reactive
const reactiveMap = new Map()
function isReactive(target){
if(reactiveMap.has(target)){
return true
}
return false
}
export function reactive(target){
//检查是否已经被代理
if(isReactive(target)){
return target
}
return createReactiveObject(
target,
mutableHandlers
)
}
export function createReactiveObject(target,mutableHandlers){
//检查是否为对象
if(typeof target !== 'object' || target == null){
return target
}
//Proxy 接受俩个参数 代理对象和代理方法
const newReactive = new Proxy(target,mutableHandlers)
reactiveMap.set(target,newReactive)
return newReactive
}
Get & Set
我们新建一个文件导出mutableHandlers方法供proxy使用,mutableHandlers需要有一个get与set,分别在访问和修改target时触发。get需要实现依赖收集,当访问对象属性时将对应的副作用函数收集到依赖集合,set需要实现当对象属性更改时,更新依赖,通知副函数执行。
js
import {track,trigger} from './effect.js'
const get = (target, key) => {
// target代理对象 key键名
track(target, key) // 副作用收集
// 相当于target[key]
const value = Reflect.get(target, key)
if (typeof value === 'object' && value !== null) {
return reactive(value)
}
return value
}
const set = (target, key, value) => {
// console.log('target被设置值', key, value)
const oldValue = Reflect.get(target, key)
// 比较是否更新
if (oldValue !== value) {
const result = Reflect.set(target, key, value)
trigger(target, key, value, oldValue)
return result
}
return true // 如果值没有变化,返回true表示设置成功
}
export const mutableHandlers = {
get,
set
}
副作用的收集与触发
接下来,我们要完成依赖收集函数track和副作用触发函数trigger。做之前我们要思考一下他们要做的事情: track在get时触发,主要负责将副作用函数effect载入targetMap中,tigger在set时触发 主要负责执行副作用函数。
提一嘴 *WeakMap是es6的新特性 特殊的是键必须是引用类型 *
js
// 存储依赖关系
const targetMap = new WeakMap() // 可以看set({}:map())
let activeEffect //当前执行的副作用函数
// 副作用的执行函数
export const effect = (callback) => {
activeEffect = callback
callback()
activeEffect = null
}
//依赖收集
export const track = (target, key) => {
// 如果该依赖没有副作用直接返回
if (!activeEffect) return
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map())) // 第一次收集该依赖
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set())) // 依赖的第一个副作用
}
dep.add(activeEffect)
}
//触发
export const trigger = (target, key) => {
const depsMap = targetMap.get(target)
if (!depsMap) return
const dep = depsMap.get(key)
if (dep) {
dep.forEach(effect => effect())
}
}
测试
到此为止,我们已经简单完成reactive的demo。接下来新建一个vue文件来测试一下这个简易的reactiveDemo
vue
<template>
<div>
<button @click="handleClick">count++</button>
</div>
</template>
<script setup>
import {reactive} from './utils/reactivity/reactive.js'
import {effect} from './utils/reactivity/effect.js'
const count = reactive({
value: 0
})
effect(()=>{
console.log('count的值是:', count.value)
})
effect(()=>{
console.log(count.value, '正在执行计算')
})
effect(()=>{
console.log(count.value, '正在渲染页面')
})
const handleClick = ()=>{
count.value++
}
</script>
当我们点击按钮触发count.value++时,到触发代理proxy的set,执行targget,从而触发此前访问过count.value相关的副作用函数,完成更新。

总结
reactive是Vue中实现响应式的Api,它通过proxy实现代理,为ref代理对象提供支持。reactive不是"多余选项"了,而是vue响应式的核心支柱。
而要实现reactive的核心在于:
- 使用proxy代理
- 收集与触发副作用函数
*以上是小编在学习过程中的一点小见解 如果有写得不对的 欢迎在评论区指出 *
