Reactive 与 Ref
ref和reactive是Vue.js中用于创建响应式数据的两个重要概念,它们各自具有不同的应用场景。正常情况下ref和reactive都具有深层响应性。
Reactive
介绍
reactive()
将使对象本身具有响应性,其行为就和普通对象一样,不同的是,Vue
能够拦截对响应式对象所有属性的访问和修改,以便进行依赖追踪和触发更新。
reactive()
返回的是一个原始对象的 Proxy,它和原始对象是不相等的,且只有代理对象是响应式的,更改原对象不会触发更新。且在同一个对象上调用 reactive() 会返回相同的代理,在一个代理上调用 reactive()
会返回它自己。
const raw = {}
const proxy = reactive(raw)
// 代理对象和原始对象不是全等的
console.log(proxy === raw) // false
// 在同一个对象上调用 reactive() 会返回相同的代理
console.log(reactive(raw) === proxy) // true
// 在一个代理上调用 reactive() 会返回它自己
console.log(reactive(proxy) === proxy) // true
Reactive简单复现
- 接受一个参数,判断这个参数是否是对象
- 创建拦截器对象
handler
,设置get/set/deleteProperty - 返回Proxy对象
发布订阅模式
let activeEffect = null
let targetMap = new WeakMap()
export function effect(callback) {
activeEffect = callback
console.log('start')
callback()// 访问响应式对象属性,去收集依赖
console.log('end')
activeEffect = null
}
// 搜集依赖(订阅)
export function track(target, key) {
console.log(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 function trigger (target, key) {
const depsMap = targetMap.get(target)
if(!depsMap) return
const dep = depsMap.get(key)
if (dep) {
// 找到集合后,执行它里面的每一个effect函数
dep.forEach(effect => {
effect()
})
}
}
Reactive
const isObject = obj => obj != null && typeof obj === 'object'
const hasOwn = (target, key) => Object.prototype.hasOwnProperty.call(target, key)
export function reactive(target) {
// 判断传递的参数是否是Obj类型 不是则直接返回 是的返回代理对象
if(!isObject(target)) return
const handler = {
get(target, key, receiver) {
// 收集依赖
track(target, key)
console.log("获取" + key + "值")
let res = Reflect.get(target, key, receiver)
// 递归调用
return isObject(res) ? reactive(res) : res
},
set(target, key, value, receiver) {
let res = true
const odlValue = Reflect.get(target, key, receiver)
// 新增 != 旧值触发更新
if (odlValue !== value) {
res = Reflect.set(target, key, value, receiver)
console.log("设置" + key + value)
// 触发更新
trigger(target, key)
}
return res
},
deleteProperty(target, key) {
const hasKey = hasOwn(target, key)
const result = Reflect.deleteProperty(target, key)
// 判断是否有key 且删除成功
if (hasKey && result) {
console.log("删除" + key + "值")
// 触发更新
trigger(target, key)
}
return result
}
}
return new Proxy(target, handler)
}
html
<html>
<body>
<script type="module">
import { reactive, effect } from './js/reactive.js'
let test = reactive({
name: '张三',
request: { code: '200' },
price: 500,
count: 5
})
let total
effect(() => {
total = test.price * test.count
})
console.log('total'+total)
test.price = 4000
console.log('total'+total)
test.count = 1
console.log('total'+total)
</script>
</body>
</html>
上述代码的数据模型
Ref
介绍
ref()
接收参数,并将其包裹在一个带有 .value
属性的 ref 对象中返回,但在模板中使用ref
时,不需要附加.value
,因为ref
在模板中会自动解包。
<script setup>
//在组件模板中访问 ref,需要在组件的 `setup()` 函数中声明并返回它们
import { ref } from 'vue'
const count = ref(0)
function increment() {
// 在 JavaScript 中需要 .value
count.value++
}
</script>
<template>
<!-- 模板中使用 -->
<button @click="increment">
{{ count }}
</button>
</template>
Vue3
有了Reactive
创建响应式变量, 为什么还要有Ref
?
在标准的JavaScript中,检测普通变量的访问或修改是行不通的。ES6新增的Proxy也无法绑定原始值,但Vue3
利用getter
和 setter
方法来拦截对象属性的get
和 set
操作。
另一方面,ref
不同于普通变量,它可以做到跟reactive
相同的将响应式数据传递给函数,并保留对最新值和响应式连接的访问。
Ref
可以持有任何类型的值,包括深层嵌套的对象、数组或者 JavaScript 内置的数据结构,比如 Map
。
Ref简单复现
-
判断接收的参数类型
- Ref创建的对象: 直接返回
- 普通对象: 内部调用reactive来创建响应式代理
- 基本数据类型(如字符串、数字、布尔值等): 创建一个只有value属性的响应式对象
-
创建
ref
对象并且只有value
属性,且这个value
属性具有get
和set
Ref
export function ref(raw) {
// 判断 raw 是否是ref 创建的对象,如果是的话直接返回
if (isObject(raw) && raw.__v_isRef) return
// 如果raw 是对象的话,调用 reactive转换为响应式对象 否则直接返回row本身
let value = isObject(raw) ? reactive(raw) : raw
const r = {
__v_isRef: true,
get value() {
// 收集依赖
console.log('get')
track(r, 'value')
return value
},
set value(newValue) {
console.log('设置' + newValue)
if (newValue !== value) {
raw = newValue
value = isObject(raw) ? reactive(raw) : raw
trigger(r, 'value')
}
}
}
return r
}
html
<html>
<body>
<script type="module">
import { effect, ref } from './js/reactive.js'
const price = ref(500)
const count = ref(1)
let total = 0
effect(() => {
total = price.value * count.value
})
console.log('total'+total)
price.value = 4000
console.log('total'+total)
count.value = 1
console.log('total'+total)
</script>
</body>
</html>
Reactive 与 Ref 的不同
Reactive
- 有限的值类型: 只能把对象转换成响应式对象
- 不能直接替换整个对象: 直接替换整个对象会使得前一个响应性连接丢失。
let state = reactive({ count: 0 })
// 上面的 ({ count: 0 }) 引用将不再被追踪
// (响应性连接已丢失!)
state = reactive({ count: 1 })
- 对解构操作不友好:将响应式对象的原始类型属性解构为本地变量时,或者将该属性传递给函数时,我们将丢失响应性连接。
const state = reactive({ count: 0 })
// 当解构时,count 已经与 state.count 断开连接
let { count } = state
// 不会影响原始的 state
count++
// 该函数接收到的是一个普通的数字,并且无法追踪 state.count 的变化
// 因此我们必须传入整个对象以保持响应性
callSomeFunction(state.count)
Ref
- 任何类型的值: 基本数据类型(如字符串、数字、布尔值等),还包括深层嵌套的对象、数组或者 JavaScript 内置的数据结构。
- 能直接替换整个对象:并不会丢失响应式,但在替换过程中需要确保新的对象与原始对象具有相同的响应式属性。缺少某个属性或者属性类型不匹配,可能会导致响应式失效或数据更新不正确。
const objectRef = ref({ count: 0, name: 'Alice' });
objectRef.value = { count: 1, name: 'Bob' };
- 解构操作友好
总而言之,如果一个对象中的成员非常多的时候,使用ref
书写并不方便,因为总要带着value
属性,如果只有一个响应式数据时候,使用ref
会比较方便。但reactive
存在上述限,因此 使用ref()
作为声明响应式状态的主要 API。
Vue3响应式基础官方文档:https://cn.vuejs.org/guide/essentials/reactivity-fundamentals.html#limitations-of-reactive
哔哩哔哩视频:https://b23.tv/8iUZJKr