🍍Pinia:Vue 3 的 State Management 精髓
前言
在前端开发领域,构建复杂且交互丰富的Web应用程序已成为常态。并且随着应用规模的不断增长,状态管理逐渐成为开发过程中不可或缺的一环。传统的状态管理解决方案,如Vuex,虽然强大,但在某些场景下可能会显得繁琐,尤其是对于中型到大型项目。正是在这种背景下,Pinia就出现啦,Pinia不仅继承了Vue 3的响应式系统,还提供了更为现代化的API,使得状态管理变得更加简单和灵活。
将从Pinia的基础概念出发,再通过一系列实例和代码示例,你将学会如何在你的项目中有效集成Pinia,以及如何利用它的模块化结构来构建可扩展且维护良好的状态管理解决方案,快和我一起往下看看吧~
pinia的特点
- 简洁性:Pinia 的 API 设计更加简洁,减少了样板代码;
- 响应式:Pinia 直接利用 Vue 3 的响应式系统,无需显式的 getter 或 watcher;
- 模块化:Pinia 支持模块化的状态管理,允许开发者将状态分割成独立的 store,每个 store 可以拥有自己的状态、getter 和 action;
- 易用性:Pinia 提供了直观的类型推断;
- 性能:由于 Pinia 依赖于 Vue 3 的响应式系统,它在性能上表现优秀,能够高效地处理状态变更和视图更新。
正文
下面代码的文件位置:
普通的父子通信
实现一个这样的效果:
父向子传递title
,子向父传递一个自定义事件onSub
和success
:
子组件(child.vue)
child.vue文件在components文件夹中:
- 给点击按钮时会触发
submit
方法; - 在
submit
方法中,使用defineEmits
函数定义了一个名为'onSub'
的自定义事件; - 通过
emit('onSub', success)
触发这个自定义事件,并传递一个名为'success'
的数据。
<template>
<div>
<h2>{{ msg }}</h2>
<button @click="submit">提交</button>
</div>
</template>
<script setup>
defineProps({
msg: {
typeof: String,
default: 'hello'
}
})
const success = '提交成功';
const emits = defineEmits(['onSub'])
const submit = () => {
emits('onSub', success)
}
</script>
<style lang="scss" scoped>
</style>
父组件(App.vue)
- 引入了
Child
组件。 - 使用
@onSub="handle"
监听Child
组件触发的'onSub'
事件。 - 当
'onSub'
事件被触发时,会执行handle
函数,并将Child
组件传递的数据'success'
赋值给res
变量。 - 最后在模板中显示
res
的值。
<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;
}
</script>
<style lang="scss" scoped>
</style>
通过公共变量通信
定义一个文件global.js
:公共的响应式变量num = 0
import { ref } from 'vue';
export const num = ref(0);
定义一个App2.vue
,记得在main.js
里把import App from './App.vue
'改为import App from './App2.vue'
App2.vue
:
<template>
<div>
<Add />
<Count />
</div>
</template>
<script setup>
import Add from './components/add.vue';
import Count from './components/count.vue';
</script>
<style lang="scss" scoped>
</style>
在components
文件夹下新建两个文件夹:
add.vue
:实现对num
进行加一的操作
<template>
<div>
<button @click="num++">add -- {{ num }}</button>
</div>
</template>
<script setup>
import { num } from '@/global.js';
</script>
<style lang="scss" scoped>
</style>
count.vue
:实现对num
进行实时渲染的操作
<template>
<div>
<h2>{{ num }}</h2>
</div>
</template>
<script setup>
import { num } from '@/global.js';
</script>
<style lang="scss" scoped>
</style>
实现效果:
这种共享变量的方法确实可以实现数据的共享,但是有一些缺点:
- 全局污染: 在大型应用程序中,如果大量使用这种全局变量的方式,很容易造成命名冲突和代码维护困难。
- 缺乏可扩展性: 随着应用程序的增长,管理全局变量会变得越来越复杂。很难对状态进行模块化和分层管理。
- 缺乏工具支持: 全局变量不像 Pinia 那样提供丰富的开发工具支持,比如时间旅行调试、持久化、热更新等。
- 不适合 SSR: 全局变量在服务端渲染(SSR)场景下可能会出现问题,因为每个请求都会共享同一个全局状态。
pinia通信
安装教程
通过npm或yarn安装Pinia:
npm install pinia
或者
yarn add pinia
pinia的defineStore()
方法和它的几个状态
defineStore()
方法:
- 用于定义一个 Store。Store 是 Pinia 中的状态容器,它包含了状态、Getter 和 Actions。
状态:
state
:state
是一个函数,它返回 Store 的初始状态。这个函数应该总是返回一个对象,其中的属性就是 Store 的状态。Getters
:getters
是用于从 Store 的状态派生出其他状态的方法。它们接收 Store 的状态作为参数,并返回新的计算属性。Actions
:actions
是用于改变 Store 状态的方法。它们可以包含任意的业务逻辑,包括异步操作。Actions 接收 Store 的状态和其他 actions 作为上下文,允许你更新状态或调用其他 actions。
创建一个store
文件夹,在该文件夹下创建一个子文件index.js
:
import { createPinia } from 'pinia';
import pinisPluginPersist from 'pinia-plugin-persist';
const store = createPinia();
store.use(pinisPluginPersist);
export default store;
引入到main.js
中:
import store from './store';
import { createApp } from 'vue'
import App from './App3.vue'
createApp(App).use(store).mount('#app')
接下来就可以去使用pinia
了,在store
下创建文件user.js
:
-
定义 Store:使用
defineStore
函数定义了一个名为"user"
的 store。这个 store 就是用于管理用户相关的状态和逻辑。 -
State:
state
函数返回一个对象,该对象表示 store 的初始状态。在这个例子中,初始状态包含一个userInfo
对象,包含name
和age
、sex
属性。 -
Actions:
actions
对象定义了三个方法:
changeUserName
: 用于更新userInfo.name
属性。changeUserAge
: 用于更新userInfo.age
属性。changeUserSex
: 用于更新userInfo.sex
属性。
这些 action 方法可以在组件中调用,以改变 store 中的状态。
-
Getters:
getters
对象定义了一个计算属性afterAge
,它会返回userInfo.age
加 10 的结果。这个计算属性可以在组件中使用,就像使用组件的 computed 属性一样。 -
persist:开启持久化功能,也就是即使用户刷新页面或关闭浏览器,Store 中的一些或全部状态也会被保存,并在下次访问时恢复。
- 安装插件:
npm install pinia-plugin-persistedstate
- 在
store
下的index.js
引入import pinisPluginPersist from 'pinia-plugin-persist';
- 在
store
下的index.js
使用插件:store.use(pinisPluginPersist);
- 在
store
下的user.js
进行配置。
persist: { // 开启数据持久化
enabled: true, // 可枚举
strategies: [
{
paths: ['userInfo'],
storage: localStorage,
}
]
}
user.js
:
import { defineStore } from 'pinia'; // defineStore 是 store的一部分
export const useUserStore = defineStore({
id: 'user',
state:() => { // 数据源
return {
userInfo: {
name: 'ltt',
age: 18,
sex: 'girl'
}
}
},
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,
}
]
}
})
在components
下创建两个文件:
user.vue
:对userInfo进行渲染
<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 sex = userStore.userInfo.sex; // 非响应式
const {userInfo} = storeToRefs(userStore); // 把整个仓库的数据源变成响应式的
console.log(userStore);
</script>
<style lang="scss" scoped>
</style>
updateUser.vue
:对userInfo进行操作
<template>
<button @click="changeName">修改仓库中的用户姓名</button>
<button @click="changeAge">修改仓库中的用户年龄</button>
<button @click="changeSex">修改仓库中的用户性别</button>
</template>
<script setup>
import {useUserStore} from "../store/user";
const userStore = useUserStore();
const changeName = () => {
// userStore.userInfo.name = "lxp"; // 不要这种代码
userStore.changeUserName("lxp");
};
const changeAge = () => {
userStore.changeUserAge(1);
};
const changeSex = () => {
userStore.changeUserSex("boy");
}
</script>
<style lang="scss" scoped>
</style>
App3.vue
:
<template>
<div>
<User />
<updateUser />
</div>
</template>
<script setup>
import User from '@/components/user.vue'
import updateUser from '@/components/updateUser.vue'
</script>
<style lang="scss" scoped>
</style>
实现效果:
结语
转载自:https://juejin.cn/post/7391746098681856038