Vue.js 的下一步:深入探索 Pinia 的强大功能
引言
在现代前端开发中,状态管理是一个不可忽视的重要部分。随着应用复杂度的增加,状态管理的难度也随之提升。Vue 3作为一种流行的前端框架,其生态系统中提供了多种状态管理方案,而Pinia则作为Vuex的替代品,越来越受到开发者的青睐。
Pinia不仅继承了Vuex的优势,还在简洁性和性能上进行了优化,使得开发者能够更方便地管理应用状态。今天,我们将从零开始,逐步深入,带你全面了解Pinia的使用方法和最佳实践。不论你是Vue的新手,还是经验丰富的开发者,都能从中找到适合自己的内容。相信通过今天的学习,你将掌握Pinia的核心概念,并能够在实际项目中灵活应用。
准备好了吗?让我们一起开始这段Pinia的学习之旅吧!
正文
在开始之前,我们先来回顾一下父子组件之间通讯是怎么样的,实现如下效果:
<template>
<Child :msg="title" @onSub="handle"/>
<h1>{{ res }}</h1>
</template>
<script setup>
import Child from '@/components/child.vue';
import { ref } from 'vue';
const title = "hello world"; // 父组件向子组件传递的消息
const res = ref(''); // 用于接收子组件的消息
const handle = (e) => {
console.log(e);
res.value = e; // 将子组件传来的消息赋值给res
}
</script>
<style lang="css" scoped>
</style>
子组件child.vue
<template>
<div>
<h2>{{ msg }}</h2>
<button @click="submit">提交</button>
</div>
</template>
<script setup>
defineProps({
msg: {
type: String,
default: 'hello'
}
})
const emits = defineEmits(['onSub']); // 定义子组件的事件
const success = '提交成功'; // 要传递给父组件的数据
const submit = () => {
emits('onSub', success); // 触发事件,将数据传递给父组件
}
</script>
<style lang="css" scoped>
</style>
当父组件渲染时,它会显示子组件,并将title
作为msg
属性传递给子组件。子组件显示msg
的内容。当点击“提交”按钮时,子组件会触发onSub
事件,将success
字符串传递给父组件,父组件通过handle
函数接收并显示传递的消息。
接下来我们想要通过设置一个全局的公共变量来进行通信,实现下面的一个效果:
我们先创建一个global.js
文件,定义公共的响应式变量,并抛出。
import { ref } from "vue";
export const num = ref(0);
创建App2.vue
文件,并且将main.js中的导入路径改为import App from './App2.vue'
,在后续创建的新文件操作时,也要进行这个操作。
<template>
<Add />
<Count />
</template>
<script setup>
import Add from '@/components/add.vue'
import Count from '@/components/count.vue'
</script>
<style lang="css" scoped></style>
在App2.vue
中我们需要两个组件,因此,在components
文件夹中,我们新创建add.vue
和count.vue
组件。
其中,add.vue
组件中,我们通过绑定点击事件对num
进行加1的操作,count.vue
组件中,对每次num++
的值进行渲染。
add.vue
<template>
<div>
<button @click="num++">add</button>
</div>
</template>
<script setup>
import { num } from '@/global.js';
</script>
<style lang="css" scoped>
</style>
count.vue
<template>
<div>
<h2>{{ num }}</h2>
</div>
</template>
<script setup>
import { num } from '@/global.js';
</script>
<style lang="css" scoped>
</style>
使用共享变量的方法在小型应用中确实可以实现数据共享,但在复杂应用中存在一些缺点:
- 命名冲突:
当应用变大时,全局变量的数量可能会增加,很容易发生命名冲突,导致意想不到的错误。
- 缺乏结构:
这种方法没有明确的组织和结构,随着应用的复杂性增加,代码会变得难以维护和理解。
- 调试困难:
由于所有组件都可以直接修改全局变量,追踪状态的变化源变得困难,增加了调试的难度。
- 无法持久化:
全局变量在页面刷新时会丢失,除非额外实现数据持久化机制。
- 难以追踪依赖关系:
组件之间的依赖关系变得不清晰,容易导致组件间耦合度增加,降低代码的可维护性和可扩展性。
Pinia通信
安装Pinia
要在Vue 3项目中使用 Pinia,首先需要安装它。你可以使用 npm 或 yarn 进行安装,我们在这里再多安装一个插件(后面会解释):
npm install pinia
或者
yarn add pinia
npm install pinia-plugin-persistedstate
配置Pinia
我们创建一个store
文件夹,并在该文件夹下创建一个index.js
文件:
import { createPinia } from "pinia";
import piniaPluginPersist from "pinia-plugin-persist";
const store = createPinia();
store.use(piniaPluginPersist);
export default store;
使用createPinia()
创建Pinia实例,并使用pinia-plugin-persist
插件来启用状态持久化,通过使用pinia-plugin-persist
插件,你可以轻松地持久化 Pinia store 的状态,确保状态在页面刷新或重新加载时不会丢失。要让某个具体的 store 支持持久化,需要在定义 store 时配置persist
选项。
在我们的 Vue 项目的入口文件main.js
中引入并use:
import { createApp } from 'vue'
import App from './App2.vue'
import store from './store'
createApp(App).use(store).mount('#app')
defineStore 方法
defineStore
方法用来定义一个新的store,它接收两个参数:
-
id:一个唯一的字符串,用来标识 store。
-
options:一个对象,用来定义 store 的 状态、getters 和 actions。
-
state
是一个函数,用来返回store的初始状态。状态是响应式的,当状态改变时,所有使用该状态的组件会自动更新。 -
getters
类似于Vue组件中的计算属性,用来从state派生出新的状态。getters可以访问store的状态,并且可以作为组件中的计算属性使用 -
actions
用来定义业务逻辑和异步操作。与Vuex的mutations和actions不同,Pinia的actions可以直接修改状态,可以是同步或异步的。 -
persist
用来开启持久化功能,即使用户刷新页面或者关闭浏览器,store 中的一些或全部状态也会被保存,并在下次访问时恢复。
-
创建 Store
接下来我们在 store 文件夹下创建一个user.js
文件,创建一个我们自己的一个名叫“user”的store:
import { defineStore } from "pinia"; // defineStore 是store的一部分
export const useUserStore = defineStore("user", {
id: 'user',
state: () => { // 数据源
return {
userInfo: {
name: '张三',
age: 18,
sex: '男'
}
}
},
actions: { // 专门用来修改state的数据
changeUserName(name) {
this.userInfo.name = name;
},
changeUserSex(sex) {
this.userInfo.sex = sex;
},
changeUserAge(n) {
this.userInfo.age += n;
}
},
getters: { // 仓库中的计算属性
afterAge(state) {
return state.userInfo.age + 10;
}
},
persist: { // 数据持久化
enabled: true,
strategies: [
{
paths: ['userInfo'],
storage: localStorage,
}
],
}
});
1. state
这里定义了userInfo
对象,包含name
、age
和sex
三个属性。
2. actions
这里定义了三个方法:
changeUserName
:修改userInfo.name
。changeUserSex
:修改userInfo.sex
。changeUserAge
:增加userInfo.age
的值。
3. getters
afterAge
:返回userInfo.age
加上10的值。
4. persist
-
enabled
:启用持久化。 -
strategies
:定义持久化策略。paths
:指定要持久化的状态路径。storage
:指定存储类型,这里使用localStorage
。
在组件中使用 store
我们先创建一个父组件App3.vue
,在这个父组件中去引入两个子组件user.vue
和updata.vue
,然后我们在两个子组件中去使用 store。
父组件App3.vue
<template>
<div>
<User/>
<UpdataUser/>
</div>
</template>
<script setup>
import User from '@/components/user.vue'
import UpdataUser from '@/components/updata.vue'
</script>
<style lang="css" scoped>
</style>
子组件user.vue
<template>
<ul>
<li>姓名:{{ userStore.userInfo.name }}</li>
<li>年龄:{{ age }}</li>
<li>十年后的年龄:{{ userStore.afterAge }}</li>
<li>性别:{{ userInfo.sex }}</li>
</ul>
</template>
<script setup>
import { useUserStore } from '@/store/user'
import { computed } from 'vue';
import { storeToRefs } from 'pinia'; // 响应式
const userStore = useUserStore();
const age = computed(() => {
return userStore.userInfo.age;
});
const { userInfo } = storeToRefs(userStore); // 响应式
</script>
<style lang="css" scoped></style>
引入了 useUserStore
并且使用了 computed
和 storeToRefs
来获取和展示 userInfo
中的数据。
子组件updata.vue
<template>
<div>
<button @click="changeName">修改仓库中的用户姓名</button>
<button @click="changeSex">修改仓库中的用户性别</button>
<button @click="changeAge">修改仓库中的用户年龄</button>
</div>
</template>
<script setup>
import { useUserStore } from '@/store/user';
const userStore = useUserStore();
const changeName = () => {
// userStore.userInfo.name = '李四' // 不要用这种方式修改仓库中的数据
userStore.changeUserName('李四')
}
const changeSex = () => {
userStore.changeUserSex('女')
}
const changeAge = () => {
userStore.changeUserAge(1)
}
</script>
<style lang="css" scoped>
</style>
在 updata.vue
组件中,引入了 useUserStore
并定义了三个方法来修改 userStore
中的数据。需要注意的是,在 Vue 3 中,直接修改 store 中的数据不是最佳实践,而应该通过定义的 actions 来修改。
效果
效果如下:
优化建议和注意事项:
-
避免直接修改 store 中的数据:
- 在
updata.vue
组件中,确保使用 store 中定义的 actions 方法来修改数据,而不是直接修改userStore.userInfo
中的属性。例如,使用userStore.changeUserName('新名字')
而不是userStore.userInfo.name = '新名字'
。
- 在
-
响应式数据获取:
- 在
user.vue
中,使用computed
和storeToRefs
来确保数据的响应式更新,特别是在展示计算属性afterAge
时。
- 在
-
多个组件共享 store:
- 在
App3.vue
中引入的两个子组件都可以通过useUserStore
获取相同的userStore
实例,确保数据的一致性和实时更新。
- 在
-
组件间通信:
- 如果需要在组件之间通信,可以通过调用 store 中的 actions 来实现。例如,在一个组件中修改了数据,另一个组件中可以通过 getters 获取更新后的数据。
通过这些优化和注意事项,你可以更好地利用 Pinia 来管理应用的状态,并确保数据在组件之间正确地共享和更新。
总结
今天学习了如何使用 Vue.js 的状态管理库 Pinia,这是一个非常强大和灵活的工具,能够帮助我们更有效地管理应用程序的状态。通过 Pinia,我们学习了如何定义和创建 store,包括定义状态(state)、计算属性(getters)、以及修改状态的方法(actions)。同时,还探讨了如何在多个组件之间共享和使用这些状态,以及如何通过插件实现状态的持久化存储,确保用户数据在页面刷新后不会丢失。
在学习过程中,我们强调了良好的编程实践,如避免直接修改 store 中的状态,而是通过 actions 来进行状态的更新,这有助于保持代码的可维护性和可扩展性。同时,利用 Vue 3 的响应式系统,我们能够轻松地在组件中订阅和反应状态的变化,确保用户界面始终保持最新的数据。
转载自:https://juejin.cn/post/7392791237618171913