响应式类方法
在配置式 API 中,我们一般将需要响应式的变量定义在 data
选项的属性里面,而在 Vue 3
的组合式 API 的 setup
方法中,我们还无法访问 data
属性,但是也可以定义响应式变量,主要用到 toRef
、toRefs
、ref
、reactive
和一些其他方法,其中有一些我们之前的代码中已经用过了,下面就来详细介绍一下它们的用法和区别。
ref 和 reactive
ref 方法
ref
方法用于 为数据添加响应式状态,既可以支持基本的数据类型,也可以支持复杂的对象数据类型,是 Vue 3
中推荐的定义响应式数据的方法,也是基本的响应式方法。需要注意的是:
-
获取和修改数据值的时候需要加
.value
。在模板中,Vue
会自动解包ref
的值,因此你可以直接使用 变量 而无需使用.value
。 -
ref
的本质是原始数据的拷贝,改变 简单类型数据 的值不会同时改变原始数据。
使用方法如示例代码 4-3-1 所示。
示例代码4-3-1 ref 方法
<div id="app">
<component-b />
</div>
const componentB = {
template: '<div>{{ name }}</div>',
setup(props) {
// 为基本数据类型添加响应式状态
const name = Vue.ref('John')
let obj = {count: 0};
// 为复杂数据类型添加响应式状态
const state = Vue.ref(obj)
console.log(name.value); // 打印 John
console.log(state.value.count); // 打印 0
let newobj = Vue.ref(obj.count) // 注意是简单数据类型
// 修改响应式数据不会影响原数据
newobj.value = 1
console.log(obj.count) // 打印 0
return {
name
}
}
}
Vue.createApp({
components: {
'component-b': componentB
}
}).mount("#app")
需要注意的是,改变的这个数据必须是简单数据类型,即一个具体的值,这样才不会影响原始数据,如上面的代码中的 obj.count
。
ref 和对象
ref
也可以用于存储对象或数组。当你使用 ref
包裹对象时,Vue
会使这个对象的引用变成响应式的。需要注意的是,如果你想要改变对象内部的属性,仍然需要通过 .value
来访问它。
ref
包裹对象时,对象的响应式是深层的,这意味着当你将一个对象包裹在 ref
中时,Vue
会将这个对象的所有属性都变成响应式的。因此,任何对该对象属性的修改都会触发视图更新。
import { ref } from 'vue';
export default {
setup() {
const user = ref({
name: 'John',
age: 30
});
const changeName = () => {
user.value.name = 'Jane'; // 通过 .value 修改对象属性
};
return {
user,
changeName
};
}
};
在这个例子中,user
是一个响应式的对象,当我们修改 user.value.name
时,Vue
会自动检测并更新视图。
ref 和数组
同样地,ref
也可以用于数组,Vue
会使数组变得响应式。
import { ref } from 'vue';
export default {
setup() {
const items = ref([1, 2, 3]);
const addItem = () => {
items.value.push(4); // 数组操作
};
return {
items,
addItem
};
}
};
在这个例子中,items
是一个响应式数组。当我们通过 items.value.push(4)
增加一个新元素时,Vue
会自动更新数组并反映到界面上。
ref 和模板引用
ref
不仅可以用于响应式数据,还可以用于在模板中创建 DOM
元素的引用。你可以通过 ref
访问 DOM
元素或组件实例。
<template>
<input ref="inputRef" type="text" />
<button @click="focusInput">Focus Input</button>
</template>
<script>
export default {
setup() {
const inputRef = ref(null);
const focusInput = () => {
// 访问 DOM 元素并调用方法
inputRef.value.focus();
};
return {
inputRef,
focusInput
};
}
};
</script>
关键点:
-
inputRef
被定义为ref(null)
,它用来引用模板中的input
元素。 -
inputRef.value
指向实际的 DOM 元素,inputRef.value.focus()
会让输入框获得焦点。 -
在模板中,
ref="inputRef"
将该 DOM 元素与inputRef
变量关联起来。
在这个例子中,inputRef
用来引用 <input>
元素,使用 inputRef.value
可以访问该 DOM
元素并调用其方法,如 focus()
。
ref 和计算属性
你也可以使用 ref
与计算属性结合,实现更复杂的逻辑。例如,计算 ref
数据的某些派生值。
import { ref, computed } from 'vue';
export default {
setup() {
const count = ref(0);
const doubledCount = computed(() => count.value * 2);
return {
count,
doubledCount
};
}
};
在这个例子中,doubledCount
是基于 count
的计算属性。当 count
发生变化时,doubledCount
会自动更新。
reactive 方法
reactive
方法用于为复杂数据添加响应式状态,只支持对象数据类型(如对象和数组),需要注意的是:
-
获取数据值的时候不需要加
.value
。 -
reactive
的参数必须是一个对象,包括JSON
数据和数组都可以,否则不具有响应式。 -
和
ref
一样,reactive
的本质也是原始数据的拷贝。 -
reactive
会对对象的每个属性(包括嵌套属性)进行递归处理,使得嵌套的对象和数组也变成响应式的。
ref
本质也是 reactive
,ref(obj)
等价于 reactive({value: obj})
,使用方法如示例代码 4-3-2 所示。
<div id="app">
<component-b />
</div>
const componentB = {
template: '<div>{{ state.count }}</div>',
setup(props) {
// 为复杂数据类型添加响应式状态
const state = Vue.reactive({count: 0})
console.log(state.count); // 打印 0
return {
state
}
}
}
Vue.createApp({
components: {
'component-b': componentB
}
}).mount("#app")
reactive
和 ref
都是用来定义响应式数据的。reactive
更推荐定义复杂的数据类型,不能直接解构,ref
更推荐定义基本类型。ref
可以简单地理解为是对 reactive
的二次包装,ref
定义数据访问的时候要多一个 .value
。
toRef 和 toRefs
toRef 方法
toRef
方法我们在之前的 setup
方法中对 props
操作时已经使用过了,其一种使用场景是为 原响应式对象 上的属性新建单个响应式 ref
,从而保持对其源对象属性的响应式连接。其接收两个参数:原响应式对象 和 属性名,返回一个 ref
数据。例如使用父组件传递的 props
数据时,要引用 props
的某个属性且要保持响应式连接时就很有用。其另一种使用场景是接收两个参数:原普通对象 和 属性名,此时可以对单个属性添加响应式 ref
,但是这个响应式 ref
的改变不会更新界面。需要注意的是:
-
获取数据值的时候需要加
.value
。 -
toRef
后的ref
数据不是原始数据的拷贝,而是引用,改变结果数据的值也会同时改变原始数据。 -
对于 原始普通数据 来说,新增加的单个
ref
改变,数据会更新,但是界面不会自动更新。
使用方法如示例代码 4-3-3 所示。
<div id="app">
<component-b user="John" />
</div>
const componentB = {
template:'<div>{{statecount.count}}</div>',
setup(props) {
const state = Vue.reactive({ // 响应数据
foo: 1,
bar: 2
})
const fooRef = Vue.toRef(state, 'foo')
fooRef.value++
console.log(state.foo) // 打印2 会影响原始数据
state.foo++
console.log(fooRef.value) // 打印3 会影响fooRef数据
const statecount = {// 普通数据
count: 0,
}
const stateRef = Vue.toRef(statecount,'count')
setTimeout(() => {
stateRef.value = 1 // 界面不会更新
console.log(statecount.count) // 打印1 会影响原始数据
},1000)
return {
statecount,
}
}
}
Vue.createApp({
components: {
'component-b': componentB
}
}).mount("#app")
上面这个 |
toRef
更多的使用场景是为对象添加单个响应式属性,而 toRefs
则是对完整的响应式对象进行转换。
toRefs 方法
toRefs
方法将原响应式对象转换为普通对象(可解构,但不丢失响应式),其中结果对象的每个属性都是指向原始对象相应属性的 ref
,同时可以将 reactive
方法返回的复杂响应式数据进行 ES 6 解构。需要注意的是:
-
获取数据值的时候需要加
.value
。 -
toRefs
后的ref
数据不是原始数据的拷贝,而是引用,改变结果数据的值也会同时改变原始数据。 -
如果我们直接对
reactive
返回的数据进行解构,这样会丢失响应式机制,采用toRefs
包装并返回则会避免这个问题。 -
toRefs
只接收响应式对象参数,不可接收普通对象参数,否则会发出警告。
|
使用方法如示例代码 4-3-4 所示。
<div id="app">
<component-b />
</div>
const componentB = {
template:'<div>{{max}},{{count}}</div>',
setup(props) {
let obj = {
count: 0,
max: 100
}
const statecount = Vue.reactive(obj)
const {count,max} = Vue.toRefs(statecount) // 方便解构
setTimeout(()=>{
statecount.max++
console.log(obj.max) // 打印101 会影响原始数据,同时界面更新
},1000)
return {
count,
max
}
}
}
Vue.createApp({
components: {
'component-b': componentB
}
}).mount("#app")
目前用得最多的还是使用 ref
和 reactive
来创建响应式对象,使用 toRefs
来转换成可以方便使用的解构的对象。
其它响应式类方法
shallowRef 方法、shallowReactive 方法和 triggerRef 方法
对于复杂对象而言,ref
和 reactive
都属于递归嵌套监听,也就是数据的每一层都是响应式的,如果数据量比较大,则非常消耗性能,而 shallowRef
和 shallowReactive
是 非递归监听,只会监听数据的第一层,如示例代码 4-3-5 所示。
<div id="app">
<component-b />
</div>
const componentB = {
template:'<div>{{shallow1.person.name}} {{shallow2.person.name}} {{shallow2.greet}}</div>',
setup(props) {
const shallow1 = Vue.shallowReactive({
greet: 'Hello, world',
person: {
name: 'John'
}
})
const shallow2 = Vue.shallowRef({
greet: 'Hello, world',
person: {
name: 'John'
}
})
setTimeout(() => {
// 这不会触发更新,因为 shallowReactive 是浅层的,只关注第一层数据
shallow1.person.name = 'Ted'
},2000)
setTimeout(() => {
// 这不会触发更新
shallow2.value.person.name = 'Ted'
// 这也不会触发更新
shallow2.value.greet = 'Hi'
// 只有当调用 triggerRef 会强制上面的更新
Vue.triggerRef(shallow2)
}, 1000)
return {shallow1, shallow2}
}
}
Vue.createApp({
components: {
'component-b': componentB
}
}).mount("#app")
注意:如果是通过 shallowRef
创建的数据,那么 Vue
监听的是 .value
变化,并不是第一层的数据的变化。因此如果要更改 shallowRef
创建的数据可以调用 xxx.value = {}
,也可以使用 triggerRef
可以强制触发之前没有被监听到的更新。另外 Vue 3
中没有提供 triggerReactive
,所以 triggerRef
不能触发 shallowReactive
创建的数据更新。
为什么需要 通常情况下,
|
readonly 方法、shallowReadonly 方法和 isReadonly 方法
从字面意思上来理解,readonly
表示只读,可以将响应式对象标识成只读,当尝试修改时会抛出警告,shallowReadonly
方法设置第一层只读,isReadonly
方法判断是否为只读对象,如示例代码4-3-6所示。
<div id="app">
<component-b />
</div>
const componentB = {
template:'<div></div>',
setup(props) {
const obj = Vue.readonly({foo: {bar: 1}})
console.log(Vue.isReadonly(obj)) // true
obj.foo.bar = 2 // 失败警告:Set operation on key "bar" failed: target is readonly.
const sobj = Vue.shallowReadonly({foo: {bar: 1}})
sobj.foo.bar = 2 // 第二层可以修改
return {}
}
}
Vue.createApp({
components: {
'component-b': componentB
}
}).mount("#app")
总结表格:
方法 | 功能 | 递归性 | 特点 | 适用场景 |
---|---|---|---|---|
readonly |
将对象或数组变为深层只读 |
深层 |
递归将每个属性变为只读,修改时抛出错误 |
防止修改对象或数组的任何属性 |
shallowReadonly |
将对象或数组变为浅层只读 |
浅层 |
只将对象的第一层属性变为只读,嵌套属性仍然可修改 |
保护顶层属性,避免递归性能开销 |
isReadonly |
检查对象是否为只读对象 |
- |
返回布尔值,判断对象是否是只读对象,支持递归判断嵌套属性 |
检查对象是否已变为只读,调试或条件判断 |
isRef 方法、isReactive 方法和 isProxy方法
isRef
方法用于判断是否是 ref
方法返回对象,isReactive
方法用于判断是否是 reactive
方法返回对象,isProxy
方法用于判断是否是 reactive
方法或者 ref
方法返回对象。
总结表格:
方法 | 用途 | 返回值 | 特点 | 适用场景 |
---|---|---|---|---|
isRef |
判断一个值是否是由 ref 创建的响应式引用 |
true 或 false |
判断值是否为 ref,可以是基本类型或对象的响应式引用。 |
调试、条件判断,确保操作的是 ref 类型。 |
isReactive |
判断一个对象或数组是否是响应式对象 |
true 或 false |
判断对象是否是通过 reactive 创建的深层响应式对象。 |
调试、条件判断,确保操作的是响应式对象。 |
isProxy |
判断一个对象是否是一个代理对象(包括 reactive 和 readonly) |
true 或 false |
判断对象是否为 Vue 的代理对象(通过 Proxy 实现)。 |
调试、类型检查,确认对象是否为代理对象。 |
toRaw 方法和 makeRaw 方法
toRaw
方法可以返回一个响应式对象的 原始普通对象,可用于临时读取数据而无须承担代理访问/跟踪的开销,也可用于写入数据而避免触发更改。
makeRaw
方法可以标记并返回一个对象,使其永远不会成为响应式对象,如示例代码4-3-7所示。
<div id="app">
<component-b />
</div>
const componentB = {
template:'<div>{{reactivecobj.bar}}</div>',
setup(props) {
const obj = { foo : 1}
const reactivecobj = Vue.reactive(obj)
const rawobj = Vue.toRaw(reactivecobj)
console.log(obj === rawobj) // true
setTimeout(()=>{
rawobj.bar = 2 // 不会触发响应式更新
},1000)
const foo = {a:1} // foo无法通过reactive成为响应式对象
console.log(Vue.isReactive(Vue.reactive(Vue.markRaw(foo)))) // false
return {
reactivecobj
}
}
}
Vue.createApp({
components: {
'component-b': componentB
}
}).mount("#app")