组合式API可以让逻辑关注点更为集中
建议总是使用 ref 而非 reactive
Vue3响应式原理
- 对于基本数据类型(使用 getter / setter),只能使用 ref,并赋值给结果的value
- 对于引用数据类型(使用Proxy),可以使用 ref 也可以使用 reactive。
reactive会将目标深层递归并返回Proxy代理。
ref对目标reactive后赋值给结果的value。
对选项式API的影响
因Vue3中的响应性是通过Proxy代理实现的,因此与Vue2不同,以下
this.someObject
和newObject
不是同一个东西,修改其中一项也不会影响到另一项:
export default {
data() {
return {
someObject: {}
}
},
mounted() {
const newObject = {}
this.someObject = newObject
console.log(newObject === this.someObject) // false
}
}
组合式API
ref(推荐使用)
- 如果传入值为基本数据类型,则通过
getter/setter
赋值给 value。如果传入值为对象,则通过reactive(Proxy代理)
深层响应式处理后,再赋值给 value。
综上:返回一个具有 value 属性的响应式对象。 - 深拷贝,与原始数据不保持联系
伪代码实现ref
function ref(value) {
const refObject = {
get value() {
track(refObject, 'value')
return value
},
set value(newValue) {
value = newValue
trigger(refObject, 'value')
}
}
return refObject
}
<template>
<button @click="increment">
{{ count }}
</button>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
function increment() {
// 在 JavaScript 中需要 .value
count.value++
}
// 不要忘记同时暴露 increment 函数
return {
count,
increment
}
}
}
</script>
ref 整个对象和仅 ref 某个对象属性的区别
const a = ref({foo:1,bar:2})
则 a.foo 和 a.bar 变化都有效果
const b = {foo:1,bar:ref(2)}
则 b.foo 变化没有效果
ref 自动解包(即不需要使用value
)
- 在模板中引用时会自动浅解包
const count = ref(0)//解包,可直接用 {{count}}
const object = { id: ref(1) }//不解包,需要用 {{object.id.value}}
- 作为响应式对象属性时自动解包(即
ref({age:ref(18)})
中,内层的ref(18)
是没有意义的,会直接变成数字18),但作为响应式数组/Map属性时不会自动解包:
const count = ref(0)
const state = reactive({
count
})
// 无需 .value
console.log(state.count) // 0
const books = reactive([ref('Vue 3 Guide')])
// 需要 .value
console.log(books[0].value)
reactive
- 传入一个引用类型(对象/数组等,但不能为基本数据类型)进行 深层递归 Proxy代理,返回 Proxy 实例(而非原始对象)。
- 深拷贝,与原始数据不保持联系
- 这是 Vue3 的响应式根基,
data()
返回的内容即通过reactive()
处理成为响应式对象
伪代码实现reactive
function reactive(obj) {
return new Proxy(obj, {
get(target, key) {
track(target, key)
return target[key]
},
set(target, key, value) {
target[key] = value
trigger(target, key)
}
})
}
- 深层递归时会解包其中的
ref
属性,同时保持相应性
const count = ref(1)
const obj = reactive({ count })
// ref 会被解包
console.log(obj.count === count.value) // true
// 它会更新 `obj.count`
count.value++
console.log(count.value) // 2
console.log(obj.count) // 2
// 它也会更新 `count` ref
obj.count++
console.log(obj.count) // 3
console.log(count.value) // 3
const count2= ref(1)
obj.count2 = count2
console.log(obj.count2) // 1
console.log(obj.count2 === count2.value) // true
- 为保证一致性,对同一对象进行
reactive
返回的结果全等,对已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
// 对一个对象ref,即将其reactive并赋值给value
console.log(reactive(proxy) === ref(raw).value) // true
- 对
reactive
对象整体重新赋值时会失去响应性
shallowRef 和 shallowReactive
分别是ref
和reactive
的浅层版本,用于优化性能。放弃对深层内容的响应,只有首层具有响应性。
shallowRef
只有.value
层具有响应性:
const state = shallowRef({ count: 1 })
// 不会触发更改
state.value.count = 2
// 会触发更改
state.value = { count: 2 }
shallowReactive
只有第一层属性具有响应性:
const state = shallowReactive({
foo: 1,
nested: {
bar: 2
}
})
// 更改状态自身的属性是响应式的
state.foo++
// ...但下层嵌套对象不会被转为响应式
isReactive(state.nested) // false
// 不是响应式的
state.nested.bar++
toValue
将值、refs 或 getters 规范化为值(比unref
多个 getters 的处理)。
reactive对象(Proxy)不会被转化,保留原内容。
readonly
接受一个对象 (不论是响应式还是普通的) 或是一个 ref,返回一个原值的只读代理。
可修改原值影响新对象,但不能直接修改新对象。
相对的,还有浅层版本shallowReadonly
import { reactive, readonly } from 'vue'
const original = reactive({ count: 0 })
const copy = readonly(original)
// 通过 original 修改 count,将会触发依赖 copy 的侦听器
original.count++
// 通过 copy 修改 count,将导致失败并出现警告
copy.count++ // 警告: "Set operation on key 'count' failed: target is readonly."
isRef
判断入参是否为 ref 实例
unref
isRef(val) ? val.value : val
的语法糖,将值、refs规范化为值。
toRef
- 可以将值、ref 实例、getters 规范化为 ref 实例
// 按原样返回现有的 ref
toRef(existingRef)
// 创建一个只读的 ref,当访问 .value 时会调用此 getter 函数
toRef(() => props.foo)
// 等同于 ref(1)
toRef(1)
- 或基于响应式对象上的一个属性,创建一个对应的 ref,并与原内容保持响应式连接。
import { reactive, ref, isRef, unref, toRef, toRefs } from "vue";
const state = reactive({});
const fooRef = toRef(state, "age");//fooRef.value 和 state.age 保持响应式连接
fooRef.value = 100;
console.log(state.age); // 100
state.age++;
console.log(fooRef.value); // 101
toRefs
传入一个响应式对象(Proxy),将该对象每个属性经过 toRef
处理后传给一个新的普通对象并返回(因此该普通对象的每一个属性都是一个 ref 实例)。
常用于解决ref、reactive对象解构赋值后,赋值目标失去相应性
const state = reactive({
foo: 1,
bar: 2
})
const stateAsRefs = toRefs(state)
state.foo++
console.log(stateAsRefs.foo.value) // 2
stateAsRefs.foo.value++
console.log(state.foo) // 3
//可用于解构 props 防止响应性丢失
setup(props) {
const { title } = toRefs(props)
console.log(title.value)
}