Vue3 中关于响应式的 API (@vue/reactivity
)有以下几个,下面通过使用不同的 Api 实现下述示例,来做一个对比和总结 :
App.vue
初始值:<input v-model.number="initial"/>
<count :initial="initial">count>
Count.vue
<div>当前值: {{count}}div>
<button @click="add">+1button>
<script>
<script>
export default {
name: "Count",
props: {
initial: Number
},
setup (props) {
/* 后续使用 computed、watchEffect、watch 不同方式补充实现*/
// ...
function add () {
count.value++
}
return {
count,
add
}
}
}
script>
Props: 注意 props 对象是响应式的,watchEffect 或 watch 会观察和响应 props 的更新;然而不要解构 props 对象,那样会使其失去响应性
赋予原始数据类型 响应式的特性
接受一个参数值并返回一个响应式且可改变的 ref 对象。ref 对象拥有一个指向内部值的单一属性 .value
。如果传入 ref 的是一个对象,将调用 reactive
方法进行深层响应转换。
function add (initial) {
initial++
}
如果 initial
是一个例如 number
的基础类型,那么当被返回时,它与内部逻辑之间的关系就丢失了!这是由于 JavaScript 中基础类型是值传递而非引用传递。
let initial = 1
add(initial)
console.log(initial) // 1
在把值作为 property 赋值给某个对象时也会出现同样的问题。一个响应式的值一旦作为 property 被赋值或从一个函数返回,而失去了响应性之后,也就失去了用途。我们可以将这个值上包裹到一个对象中再返回。
// 模拟实现
function ref (initial) {
return {value: initial}
}
function add (initial) {
initial.value++
}
let initialRef = ref(1)
add(initialRef)
console.log(initialRef.value) // 2
现在我们可以通过引用来传递计算值,也不需要担心其响应式特性会丢失了。当然代价就是:为了获取最新的值,我们每次都需要写 .value
。
注意:当 ref 作为渲染上下文的属性返回(即在 setup()
返回的对象中)并在模板中使用时,它会自动解套,无需在模板内额外书写 .value
。
<template>
<div>Hello {{countRef}}div>
<button @click="add">增加button>
template>
<script>
import { ref } from '@vue/reactivity'
export default {
name: "Test1",
setup () {
let countRef = ref(0)
function add () {
countRef.value++
}
return {
countRef,
add
}
}
};
script>
{{countRef}}
countRef.value
xxxRef
的后缀。赋予对象(Object) 响应式的特性
接收一个普通对象然后返回该普通对象的响应式代理。等同于 2.x 的 Vue.observable()
。响应式转换是“深层的”:会影响对象内部所有嵌套的属性。
<template>
<div>Hello {{state.count}}div>
<div>double {{state.double}}div>
<button @click="add">增加button>
template>
<script>
import { reactive, computed } from '@vue/reactivity'
export default {
name: "Test2",
setup () {
let count = ref(0)
const state = reactive({
count, // count: 0
double: computed(() => state.count * 2),
})
function add () {
state.count++
}
return {
state,
add
}
}
};
script>
使用 ref
和 reactive
的区别,主要源于其编写代码的风格。
// 风格 1: 将变量分离
let x = 0
let y = 0
function updatePosition(e) {
x = e.pageX
y = e.pageY
}
// --- 与下面的相比较 ---
// 风格 2: 单个对象
const pos = {
x: 0,
y: 0,
}
function updatePosition(e) {
pos.x = e.pageX
pos.y = e.pageY
}
ref
,我们实际上就是将风格 (1) 转换为使用 ref (为了让基础类型值具有响应性) 的更细致的写法。reactive
和风格 (2) 一致。我们只需要通过 reactive
创建这个对象。function useMousePosition() {
const pos = reactive({
x: 0,
y: 0,
})
return pos
}
// 这里会丢失响应性!
const { x, y } = useMousePosition()
// 这里会丢失响应性!
{ ...useMousePosition() }
// 保持响应式
{ pos: useMousePosition() }
function useMousePosition() {
// ...
return toRefs(pos)
}
// x & y 现在是 ref 形式了!
const { x, y } = useMousePosition()
ref
和 reactive
。我们推荐你在此风格下结合 IDE 使用类型系统。reactive
,然后记得在组合函数返回响应式对象时使用 toRefs
。这降低了一些关于 ref 的心智负担,但并不意味着你不需要熟悉这个概念。传入一个 getter 函数,返回一个默认不可手动修改的 ref 对象。
// 完善开头的 setup 函数
setup (props) {
const count = computed(() => props.initial)
// ...
}
count 不可修改,因此导致 add()
函数不可用;此种方式行不通!
传入一个对象(响应式或普通)或 ref,返回一个原始对象的只读代理。一个只读的代理是“深层的”,对象内部任何嵌套的属性也都是只读的。
可以使用该属性来包裹项目中的字典数据!
立即执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更时重新运行该函数。
// 完善开头的 setup 函数
setup (props) {
const count = ref(null)
watchEffect(() => {
count.value = props.initial
})
// ...
}
watch
API 完全等效于 2.x this.$watch
(以及 watch
中相应的选项)。watch
需要侦听特定的数据源,并在回调函数中执行副作用。默认情况是懒执行的,也就是说仅在侦听的源变更时才执行回调。
侦听器的数据源可以是一个拥有返回值的 getter 函数,也可以是 ref
// 完善开头的 setup 函数
setup (props) {
const count = ref(null)
// isRef(props.initial) => false
watch(() => props.initial, (initial, prvInitial) => {
count.value = initial
}, {immediate: true})
// ...
}
通过 toRef
// 完善开头的 setup 函数
setup (props) {
const count = ref(null)
watch(toRef(props, 'initial'), (initial, prvInitial) => {
count.value = initial
}, {immediate: true})
// ...
}
toRef
可以用来为一个 reactive 对象的属性创建一个 ref。这个 ref 可以被传递并且能够保持响应性。
通过 toRefs
/ 完善开头的 setup 函数
setup (props) {
const count = ref(null)
// isReactive(props) => true
const {initial} = toRefs(props)
watch(initial, (initial, prvInitial) => {
count.value = initial
}, {immediate: true})
// ...
}
toRefs
把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref ,和响应式对象 property 一一对应。