各位观众老爷,晚上好!我是今晚的主讲人,接下来咱们就来聊聊 Vue 3 里让人又爱又恨的 ref
和 reactive
,以及 reactive
为什么解构的时候需要小心翼翼,不然就容易翻车的问题。相信我,听完这次“表演”,保证你对它们俩的理解更上一层楼,以后再也不用担心在 ref
和 reactive
之间左右摇摆了。
开场白:Vue 3 的两大护法
在 Vue 3 的世界里,数据响应式是核心。而实现数据响应式的两大功臣,就是 ref
和 reactive
。它们就像是武侠小说里的左右护法,共同守护着 Vue 组件的数据安全和实时更新。但是,这哥俩的性格和使用方式却大相径庭,用错了地方,轻则代码冗余,重则 Bug 满天飞。
第一回合:ref
的自我介绍
ref
,顾名思义,就是“reference”(引用)的缩写。它就像是一个“指针”,指向一个基本类型的值,或者一个对象。你可以把它想象成一个快递单号,通过这个单号,你能找到对应的包裹(数据)。
-
特点:
- 主要用于包装基本类型(number, string, boolean 等)和对象。
- 访问和修改值需要通过
.value
属性。 - 在模板中可以直接使用,无需
.value
(Vue 会自动解包)。
-
代码示例:
import { ref } from 'vue';
export default {
setup() {
const count = ref(0); // 创建一个 ref,初始值为 0
const message = ref('Hello, Vue!'); // 创建一个 ref,初始值为 'Hello, Vue!'
const increment = () => {
count.value++; // 修改 count 的值,必须通过 .value
console.log(count.value);
};
return {
count,
message,
increment
};
},
template: `
<p>Count: {{ count }}</p>
<p>Message: {{ message }}</p>
<button @click="increment">Increment</button>
`
};
在上面的例子中,count
和 message
都是 ref
对象。在 JavaScript 代码中,我们需要通过 count.value
和 message.value
来访问和修改它们的值。但是在模板中,我们可以直接使用 {{ count }}
和 {{ message }}
,Vue 会自动帮我们解包。
第二回合:reactive
的登场
reactive
,意为“反应式的”,它专门用来处理对象和数组。它会把一个对象变成一个“代理对象”(Proxy),当对象的属性被访问或修改时,Vue 能够立即感知到,并触发相应的更新。你可以把它想象成一个布满了传感器的房间,任何风吹草动都能被立刻捕捉到。
-
特点:
- 专门用于包装对象和数组。
- 直接访问和修改对象的属性,无需
.value
。 - 对深层嵌套的对象和数组也有效。
-
代码示例:
import { reactive } from 'vue';
export default {
setup() {
const state = reactive({
name: 'Alice',
age: 30,
address: {
city: 'Beijing',
street: '某某大街'
}
});
const updateName = () => {
state.name = 'Bob'; // 直接修改 name 属性
console.log(state.name);
};
const updateCity = () => {
state.address.city = 'Shanghai'; // 直接修改 address.city 属性
console.log(state.address.city);
};
return {
state,
updateName,
updateCity
};
},
template: `
<p>Name: {{ state.name }}</p>
<p>Age: {{ state.age }}</p>
<p>City: {{ state.address.city }}</p>
<button @click="updateName">Update Name</button>
<button @click="updateCity">Update City</button>
`
};
在这个例子中,state
是一个 reactive
对象。我们可以直接通过 state.name
、state.age
和 state.address.city
来访问和修改对象的属性,Vue 会自动追踪这些变化。
第三回合:ref
vs reactive
,巅峰对决
特性 | ref |
reactive |
---|---|---|
适用类型 | 基本类型和对象 | 对象和数组 |
访问/修改方式 | .value (JS) / 直接访问 (模板) |
直接访问和修改 |
响应式原理 | 通过 value 的 getter/setter 触发 |
通过 Proxy 代理对象,拦截属性的访问和修改 |
深层响应式 | 需要手动实现深层 ref |
默认支持深层响应式 |
什么时候用 ref
?什么时候用 reactive
?
- 基本类型: 毫无疑问,
ref
是你的首选。 - 简单对象: 如果你的对象结构很简单,只有一层属性,而且你习惯了使用
.value
,那么ref
也可以胜任。 - 复杂对象/数组: 强烈建议使用
reactive
。它能让你更方便地访问和修改对象的属性,而且默认支持深层响应式。 - 需要手动控制响应式: 如果你需要更精细地控制数据的响应式行为,例如只让某些属性是响应式的,或者自定义响应式的逻辑,那么
ref
提供了更大的灵活性。
重点来了!reactive
为什么要小心解构?
这才是今天的重头戏!很多 Vue 初学者在使用 reactive
的时候,会遇到一个“坑”:解构 reactive
对象后,响应式就失效了。这是怎么回事呢?
原因分析:
reactive
的响应式原理是基于 Proxy 对象。Proxy 对象会拦截对原始对象的属性访问和修改,并在这些操作发生时通知 Vue。但是,当你解构 reactive
对象时,你实际上是创建了原始对象属性的副本,而不是 Proxy 对象的引用。也就是说,你绕过了 Proxy 对象的拦截,直接操作了原始对象的属性,Vue 自然就无法感知到这些变化了。
举个例子:
import { reactive } from 'vue';
export default {
setup() {
const state = reactive({
name: 'Alice',
age: 30
});
// 解构 state 对象
const { name, age } = state;
const updateName = () => {
name = 'Bob'; // 修改的是解构出来的变量,而不是 state.name
console.log(name); // 输出 Bob
console.log(state.name); // 输出 Alice,state.name 没有改变
};
return {
state,
name,
age,
updateName
};
},
template: `
<p>Name: {{ name }}</p>
<p>Age: {{ age }}</p>
<button @click="updateName">Update Name</button>
`
};
在这个例子中,我们解构了 state
对象,得到了 name
和 age
两个变量。当我们修改 name
的值时,实际上修改的是解构出来的变量,而不是 state.name
。因此,state.name
的值并没有改变,模板也不会更新。
如何正确解构 reactive
对象?
既然直接解构不行,那有没有办法既能解构 reactive
对象,又能保持响应式呢?当然有!Vue 提供了几个解决方案:
-
使用
toRefs
:toRefs
函数可以将reactive
对象的所有属性转换为ref
对象。这样,你就可以解构这些ref
对象,并通过.value
来访问和修改它们的值,同时保持响应式。import { reactive, toRefs } from 'vue'; export default { setup() { const state = reactive({ name: 'Alice', age: 30 }); // 使用 toRefs 将 state 的属性转换为 ref 对象 const { name, age } = toRefs(state); const updateName = () => { name.value = 'Bob'; // 修改的是 ref 对象的值,会触发响应式更新 console.log(name.value); // 输出 Bob console.log(state.name); // 输出 Bob,state.name 也会改变 }; return { state, name, age, updateName }; }, template: ` <p>Name: {{ name }}</p> <p>Age: {{ age }}</p> <button @click="updateName">Update Name</button> ` };
使用
toRefs
的优点是,你可以像使用普通ref
对象一样,通过.value
来访问和修改属性的值。但是,你需要记住,解构出来的变量都是ref
对象,需要使用.value
才能访问它们的值。 -
不解构,直接使用
state.xxx
: 这是最简单粗暴的方法,直接通过state.name
、state.age
等方式来访问和修改对象的属性。虽然代码稍微冗长一些,但是可以避免解构带来的问题。import { reactive } from 'vue'; export default { setup() { const state = reactive({ name: 'Alice', age: 30 }); const updateName = () => { state.name = 'Bob'; // 直接修改 state.name 属性,会触发响应式更新 console.log(state.name); // 输出 Bob }; return { state, updateName }; }, template: ` <p>Name: {{ state.name }}</p> <p>Age: {{ state.age }}</p> <button @click="updateName">Update Name</button> ` };
这种方法的优点是简单直接,不容易出错。但是,如果你的对象有很多属性,代码就会变得比较冗长。
-
使用
...mapState
(Vuex): 如果你使用了 Vuex,可以使用...mapState
辅助函数来将 Vuex 的 state 映射到组件的计算属性中。这样,你就可以像访问普通计算属性一样,访问 Vuex 的 state,而且保持响应式。import { mapState } from 'vuex'; export default { computed: { ...mapState(['name', 'age']) // 将 Vuex 的 state 映射到组件的计算属性中 }, methods: { updateName() { this.$store.commit('updateName', 'Bob'); // 通过 mutation 修改 Vuex 的 state } }, template: ` <p>Name: {{ name }}</p> <p>Age: {{ age }}</p> <button @click="updateName">Update Name</button> ` };
使用
...mapState
的优点是,可以将 Vuex 的 state 和组件解耦,使代码更易于维护。但是,你需要先配置 Vuex,才能使用这个方法。
总结:
ref
和reactive
是 Vue 3 中实现数据响应式的两大功臣。ref
主要用于包装基本类型和对象,需要通过.value
来访问和修改值。reactive
专门用于包装对象和数组,可以直接访问和修改对象的属性。- 解构
reactive
对象后,响应式会失效。 - 可以使用
toRefs
、不解构、或者...mapState
来解决解构带来的问题。
最后的忠告:
在使用 ref
和 reactive
的时候,一定要根据数据的类型和使用场景,选择合适的方法。不要盲目跟风,也不要过度设计。记住,代码的简洁性和可读性永远是最重要的。
好了,今天的“表演”就到这里。希望大家能够有所收获,以后在 Vue 3 的世界里,能够更加游刃有余,写出更加优雅的代码。咱们下次再见!