各位观众老爷们,大家好!今天咱们来聊聊Vue 3源码里那些让人欲罢不能的小秘密,尤其是Composition API中的ref
和reactive
,以及它们在Pinia这个状态管理库里是如何大放异彩的。准备好了吗?咱们这就开讲!
开场白:为啥要聊这个?
话说当年Vue 2用Options API的时候,代码就像一盘散沙,逻辑散落在data
、methods
、computed
里,组件稍微复杂点,代码就跟意大利面条似的,缠都缠不清。自从Vue 3祭出Composition API,那感觉就像给代码做了个外科手术,把逻辑按功能模块打包,再也不怕找不到东西了。
ref
和reactive
就是Composition API里的两把利剑,它们让我们可以更灵活、更清晰地管理组件的状态。而Pinia呢,它就是个状态管理的超级容器,把ref
和reactive
玩得溜溜的,让我们的应用状态管理变得井井有条。所以,搞懂它们之间的关系,对我们写出高效、可维护的Vue 3应用至关重要。
第一部分:ref
和reactive
:Vue 3的左右护法
首先,咱们来认识一下ref
和reactive
,它们是Vue 3响应式系统的核心API。
-
ref
:单身贵族的容器ref
就像一个盒子,专门用来装基本数据类型(number、string、boolean等等)或者单个对象。当你修改ref
的值时,Vue会自动更新视图。import { ref } from 'vue'; export default { setup() { const count = ref(0); // 创建一个ref,初始值为0 const increment = () => { count.value++; // 注意:要通过.value来访问和修改ref的值 }; return { count, increment, }; }, template: ` <div> <p>Count: {{ count }}</p> <button @click="increment">Increment</button> </div> `, };
在这个例子中,
count
就是一个ref
,它的值可以通过count.value
来访问和修改。当点击按钮时,increment
函数会修改count.value
,Vue会自动更新页面上的count
显示。重点:
ref
必须通过.value
来访问和修改值。这是因为ref
内部使用了一个对象,.value
属性就是这个对象上存储真实值的地方。 -
reactive
:大家庭的代理人reactive
则更适合处理对象和数组。它会创建一个Proxy对象,对对象的所有属性进行响应式追踪。当你修改对象或数组的属性时,Vue也会自动更新视图。import { reactive } from 'vue'; export default { setup() { const state = reactive({ name: 'Vue', age: 3, hobbies: ['coding', 'reading'], }); const addHobby = (hobby) => { state.hobbies.push(hobby); }; return { state, addHobby, }; }, template: ` <div> <p>Name: {{ state.name }}</p> <p>Age: {{ state.age }}</p> <p>Hobbies: {{ state.hobbies }}</p> <button @click="addHobby('hiking')">Add Hobby</button> </div> `, };
这里,
state
是一个reactive
对象,它的name
、age
和hobbies
属性都是响应式的。当点击按钮时,addHobby
函数会修改state.hobbies
数组,Vue会自动更新页面上的爱好列表。重点:
reactive
不需要像ref
那样使用.value
来访问和修改值,直接访问对象的属性即可。但是,reactive
只能处理对象和数组,不能直接处理基本数据类型。
ref
vs reactive
:擂台赛
特性 | ref |
reactive |
---|---|---|
适用类型 | 基本数据类型和单个对象 | 对象和数组 |
访问方式 | 通过.value 访问和修改 |
直接访问属性 |
内部实现 | 创建一个包含.value 属性的对象 |
创建一个Proxy对象 |
使用场景 | 存储单个值,例如计数器、输入框的值等 | 存储复杂的数据结构,例如用户信息、列表数据等 |
深层响应式 | 需要使用 toRef 或 toRefs |
天然深层响应式 |
第二部分:Pinia:状态管理的百宝箱
Pinia是Vue的官方推荐的状态管理库,它简单、轻量、类型安全,而且与Vue Devtools完美集成。Pinia的核心概念是Store,Store就像一个大仓库,用来存储应用的状态。
-
定义Store:仓库的蓝图
在Pinia中,我们使用
defineStore
函数来定义Store。defineStore
接受一个唯一的ID(用来区分不同的Store)和一个配置对象,配置对象可以包含state
、getters
和actions
三个属性。// stores/counter.js import { defineStore } from 'pinia'; import { ref, reactive } from 'vue'; export const useCounterStore = defineStore('counter', () => { // state:存储状态 const count = ref(0); const user = reactive({ name: 'Guest', age: 18 }) // getters:计算属性 const doubleCount = computed(() => count.value * 2); // actions:修改状态的方法 function increment() { count.value++; } function setUser(name, age) { user.name = name; user.age = age; } return { count, doubleCount, increment, user, setUser }; });
在这个例子中,我们定义了一个名为
counter
的Store,它包含一个count
状态(使用ref
创建)、一个doubleCount
计算属性和一个increment
action。 -
使用Store:从仓库取货
在组件中,我们可以通过
useCounterStore
函数来获取Store的实例,然后就可以访问Store的状态、计算属性和action了。// components/CounterComponent.vue import { useCounterStore } from '@/stores/counter'; export default { setup() { const counterStore = useCounterStore(); return { counterStore, }; }, template: ` <div> <p>Count: {{ counterStore.count }}</p> <p>Double Count: {{ counterStore.doubleCount }}</p> <p>User Name: {{ counterStore.user.name }}</p> <p>User Age: {{ counterStore.user.age }}</p> <button @click="counterStore.increment()">Increment</button> <button @click="counterStore.setUser('John', 30)">Set User</button> </div> `, };
在这个组件中,我们通过
counterStore.count
来访问count
状态,通过counterStore.doubleCount
来访问doubleCount
计算属性,通过counterStore.increment()
来调用increment
action。
第三部分:ref
和reactive
在Pinia中的完美融合
Pinia充分利用了ref
和reactive
的特性,让状态管理变得更加简单和高效。
-
ref
:存储简单状态的利器在Pinia中,我们可以使用
ref
来存储简单的状态,例如计数器、开关状态等等。由于ref
本身就是响应式的,所以当ref
的值发生变化时,所有依赖于它的组件都会自动更新。import { defineStore } from 'pinia'; import { ref } from 'vue'; export const useSettingsStore = defineStore('settings', () => { const darkMode = ref(false); // 默认关闭夜间模式 const toggleDarkMode = () => { darkMode.value = !darkMode.value; }; return { darkMode, toggleDarkMode }; });
在这个例子中,
darkMode
就是一个ref
,它存储了夜间模式的开关状态。当调用toggleDarkMode
action时,darkMode.value
的值会发生变化,所有依赖于darkMode
的组件都会自动更新。 -
reactive
:管理复杂状态的专家reactive
则更适合用来管理复杂的状态,例如用户信息、商品列表等等。由于reactive
会自动追踪对象的所有属性,所以当对象的任何属性发生变化时,所有依赖于它的组件都会自动更新。import { defineStore } from 'pinia'; import { reactive } from 'vue'; export const useUserStore = defineStore('user', () => { const user = reactive({ id: null, name: '', email: '', loggedIn: false, }); const login = (userData) => { user.id = userData.id; user.name = userData.name; user.email = userData.email; user.loggedIn = true; }; const logout = () => { user.id = null; user.name = ''; user.email = ''; user.loggedIn = false; }; return { user, login, logout }; });
在这个例子中,
user
是一个reactive
对象,它存储了用户的各种信息。当调用login
action时,user
对象的属性会发生变化,所有依赖于user
的组件都会自动更新。
Pinia源码中的ref
和reactive
(窥探内部)
虽然我们不需要深入了解Pinia的每一行代码才能使用它,但是了解Pinia如何使用ref
和reactive
可以帮助我们更好地理解Pinia的内部机制。
Pinia内部使用了Vue的响应式系统来追踪Store的状态。当我们定义Store时,Pinia会将state
中的所有ref
和reactive
对象都转换为响应式对象。这样,当Store的状态发生变化时,Pinia就可以自动通知所有依赖于它的组件进行更新。
深入一点: toRefs
的妙用
有时候,我们希望将reactive
对象中的属性单独暴露给组件,而不是直接暴露整个对象。这时,就可以使用toRefs
函数。
toRefs
函数会将reactive
对象的所有属性转换为ref
对象。这样,我们就可以在组件中单独访问和修改reactive
对象的属性,而不需要通过整个对象。
import { defineStore } from 'pinia';
import { reactive, toRefs } from 'vue';
export const useProductStore = defineStore('product', () => {
const product = reactive({
name: 'Awesome Product',
price: 99.99,
description: 'This is a great product!',
});
// 使用 toRefs 将 product 对象的所有属性转换为 ref 对象
const { name, price, description } = toRefs(product);
const updatePrice = (newPrice) => {
product.price = newPrice;
};
return {
name, // name 是一个 ref
price, // price 是一个 ref
description, // description 是一个 ref
updatePrice,
};
});
在组件中使用:
<template>
<div>
<p>Name: {{ name }}</p>
<p>Price: {{ price }}</p>
<p>Description: {{ description }}</p>
<button @click="updatePrice(129.99)">Update Price</button>
</div>
</template>
<script>
import { useProductStore } from '@/stores/product';
import { mapStores } from 'pinia';
export default {
computed: {
...mapStores(useProductStore), //或者你直接使用组合式 API
},
methods:{
updatePrice(newPrice){
this.useProductStore.updatePrice(newPrice);
}
},
setup() {
const productStore = useProductStore();
return {
name: productStore.name,
price: productStore.price,
description: productStore.description,
};
},
};
</script>
总结:ref
和reactive
,Pinia的左膀右臂
ref
和reactive
是Vue 3响应式系统的基石,它们让我们可以更灵活、更清晰地管理组件的状态。Pinia充分利用了ref
和reactive
的特性,让状态管理变得更加简单和高效。
ref
适合存储简单状态,例如计数器、开关状态等等。reactive
适合管理复杂状态,例如用户信息、商品列表等等。toRefs
可以将reactive
对象的属性转换为ref
对象,方便在组件中单独访问和修改属性。
掌握了ref
和reactive
,以及它们在Pinia中的应用,你就可以写出更高效、更可维护的Vue 3应用了。
结束语:代码的世界,永无止境
好了,今天的分享就到这里。希望大家能够通过这次讲座,对Vue 3的ref
和reactive
,以及它们在Pinia中的应用有更深入的理解。
记住,代码的世界永无止境,只有不断学习和实践,才能成为真正的编程高手。下次再见!