likes
comments
collection
share

Vue.js 的下一步:深入探索 Pinia 的强大功能

作者站长头像
站长
· 阅读数 20

引言

在现代前端开发中,状态管理是一个不可忽视的重要部分。随着应用复杂度的增加,状态管理的难度也随之提升。Vue 3作为一种流行的前端框架,其生态系统中提供了多种状态管理方案,而Pinia则作为Vuex的替代品,越来越受到开发者的青睐。

Pinia不仅继承了Vuex的优势,还在简洁性和性能上进行了优化,使得开发者能够更方便地管理应用状态。今天,我们将从零开始,逐步深入,带你全面了解Pinia的使用方法和最佳实践。不论你是Vue的新手,还是经验丰富的开发者,都能从中找到适合自己的内容。相信通过今天的学习,你将掌握Pinia的核心概念,并能够在实际项目中灵活应用。

准备好了吗?让我们一起开始这段Pinia的学习之旅吧!

正文

在开始之前,我们先来回顾一下父子组件之间通讯是怎么样的,实现如下效果:

Vue.js 的下一步:深入探索 Pinia 的强大功能

父组件`App.vue`
<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函数接收并显示传递的消息。

接下来我们想要通过设置一个全局的公共变量来进行通信,实现下面的一个效果:

Vue.js 的下一步:深入探索 Pinia 的强大功能

我们先创建一个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.vuecount.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>

使用共享变量的方法在小型应用中确实可以实现数据共享,但在复杂应用中存在一些缺点:

  1. 命名冲突

当应用变大时,全局变量的数量可能会增加,很容易发生命名冲突,导致意想不到的错误。

  1. 缺乏结构

这种方法没有明确的组织和结构,随着应用的复杂性增加,代码会变得难以维护和理解。

  1. 调试困难

由于所有组件都可以直接修改全局变量,追踪状态的变化源变得困难,增加了调试的难度。

  1. 无法持久化

全局变量在页面刷新时会丢失,除非额外实现数据持久化机制。

  1. 难以追踪依赖关系

组件之间的依赖关系变得不清晰,容易导致组件间耦合度增加,降低代码的可维护性和可扩展性。

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,它接收两个参数:

  1. id:一个唯一的字符串,用来标识 store。

  2. 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对象,包含nameagesex三个属性。

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.vueupdata.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 并且使用了 computedstoreToRefs 来获取和展示 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 来修改。

效果

效果如下:

Vue.js 的下一步:深入探索 Pinia 的强大功能

优化建议和注意事项:

  1. 避免直接修改 store 中的数据

    • updata.vue 组件中,确保使用 store 中定义的 actions 方法来修改数据,而不是直接修改 userStore.userInfo 中的属性。例如,使用 userStore.changeUserName('新名字') 而不是 userStore.userInfo.name = '新名字'
  2. 响应式数据获取

    • user.vue 中,使用 computedstoreToRefs 来确保数据的响应式更新,特别是在展示计算属性 afterAge 时。
  3. 多个组件共享 store

    • App3.vue 中引入的两个子组件都可以通过 useUserStore 获取相同的 userStore 实例,确保数据的一致性和实时更新。
  4. 组件间通信

    • 如果需要在组件之间通信,可以通过调用 store 中的 actions 来实现。例如,在一个组件中修改了数据,另一个组件中可以通过 getters 获取更新后的数据。

通过这些优化和注意事项,你可以更好地利用 Pinia 来管理应用的状态,并确保数据在组件之间正确地共享和更新。

总结

今天学习了如何使用 Vue.js 的状态管理库 Pinia,这是一个非常强大和灵活的工具,能够帮助我们更有效地管理应用程序的状态。通过 Pinia,我们学习了如何定义和创建 store,包括定义状态(state)、计算属性(getters)、以及修改状态的方法(actions)。同时,还探讨了如何在多个组件之间共享和使用这些状态,以及如何通过插件实现状态的持久化存储,确保用户数据在页面刷新后不会丢失。

在学习过程中,我们强调了良好的编程实践,如避免直接修改 store 中的状态,而是通过 actions 来进行状态的更新,这有助于保持代码的可维护性和可扩展性。同时,利用 Vue 3 的响应式系统,我们能够轻松地在组件中订阅和反应状态的变化,确保用户界面始终保持最新的数据。

Vue.js 的下一步:深入探索 Pinia 的强大功能

转载自:https://juejin.cn/post/7392791237618171913
评论
请登录