Pinia进阶setup函数式写法封装到企业项目
目录
- 开场
- 认识Pinia
- Pinia与Vuex代码分割机制
- Pinia的常规用法
- 1. 安装
- 2. 挂载全局实例
- 3. 创建第一个store
- 4. 业务组件对store的调用
- 5. 良好的编程习惯
- 企业项目封装攻略
- 1. 全局注册机
- 重复打包问题
- 创建总入口
- 总线注册
- 打包解耦
- 2. Store组管理
- 场景分析
- 组级Store创建
- 单元Store
- 业务组件调用
- 效果
- 落幕
开场
Hello大家好,相信在座各位假如使用Vue生态开发项目情况下,对Pinia状态管理库应该有所听闻或正在使用,假如还没接触到Pinia,这篇文章可以帮你快速入门,并如何在企业项目中更优雅封装使用。
本文先给大家阐述如何去理解、使用Pinia,最后讲怎样把Pinia集成到工程中,适合大多数读者,至于研读Pinia的源码等进阶科普,会另外开一篇文章细述。另外,本文的所有demo,都专门开了个GitHub项目来保存,有需要的同学可以拿下来实操一下。
认识Pinia
Pinia读音:/piːnjʌ/,是Vue官方团队推荐代替Vuex的一款轻量级状态管理库。 它最初的设计理念是让Vue Store拥有一款Composition API方式的状态管理库,并同时能支持 Vue2.x版本的Option API 和 Vue3版本的setup Composition API开发模式,并完整兼容Typescript写法(这也是优于Vuex的重要因素之一),适用于所有的vue项目。
比起Vuex,Pinia具备以下优点:
- 完整的 TypeScript 支持:与在 Vuex 中添加 TypeScript 相比,添加 TypeScript 更容易
- 极其轻巧(体积约 1KB)
- store 的 action 被调度为常规的函数调用,而不是使用 dispatch 方法或 MapAction 辅助函数,这在 Vuex 中很常见
- 支持多个Store
- 支持 Vue devtools、SSR 和 webpack 代码拆分
Pinia与Vuex代码分割机制
上述的Pinia轻量有一部分体现在它的代码分割机制中。
举个例子:某项目有3个store「user、job、pay」,另外有2个路由页面「首页、个人中心页」,首页用到job store,个人中心页用到了user store,分别用Pinia和Vuex对其状态管理。

先看Vuex的代码分割: 打包时,vuex会把3个store合并打包,当首页用到Vuex时,这个包会引入到首页一起打包,最后输出1个js chunk。这样的问题是,其实首页只需要其中1个store,但其他2个无关的store也被打包进来,造成资源浪费。

Pinia的代码分割: 打包时,Pinia会检查引用依赖,当首页用到job store,打包只会把用到的store和页面合并输出1个js chunk,其他2个store不耦合在其中。Pinia能做到这点,是因为它的设计就是store分离的,解决了项目的耦合问题。
Pinia的常规用法
事不宜迟,直接开始使用Pinia「本文默认使用Vue3的setup Composition API开发模式」。
假如你对Pinia使用熟悉,可以略过这part
1. 安装
yarn add pinia # or with npm npm install pinia
2. 挂载全局实例
import { createPinia } from 'pinia'
app.use(createPinia())
3. 创建第一个store
在src/store/counterForOptions.ts创建你的store。定义store模式有2种:
使用options API模式定义,这种方式和vue2的组件模型形式类似,也是对vue2技术栈开发者较为友好的编程模式。
import { defineStore } from 'pinia';
// 使用options API模式定义
export const useCounterStoreForOption = defineStore('counterForOptions', {
// 定义state
state: () => {
return { count1: 1 };
},
// 定义action
actions: {
increment() {
this.count1++;
}
},
// 定义getters
getters: {
doubleCount(state) {
return state.count1 * 2;
}
}
});
使用setup模式定义,符合Vue3 setup的编程模式,让结构更加扁平化,个人推荐推荐使用这种方式。
import { ref } from 'vue';
import { defineStore } from 'pinia';
// 使用setup模式定义
export const useCounterStoreForSetup = defineStore('counterForSetup', () => {
const count = ref<number>(1);
function increment() {
count.value++;
}
function doubleCount() {
return count.value * 2;
}
return { count, increment, doubleCount };
});
上面2种定义方式效果都是一样的,我们用defineStore方法定义一个store,里面分别定义1个count的state,1个increment action 和1个doubleCount的getters。其中state是要共享的全局状态,而action则是让业务方调用来改变state的入口,getters是获取state的计算结果。
之所以用第一种方式定义是还要额外写getters、action关键字来区分,是因为在options API模式下可以通过mapState()、mapActions()等方法获取对应项;但第二种方式就可以直接获取了(下面会细述)。
4. 业务组件对store的调用
在src/components/PiniaBasicSetup.vue目录下创建个组件。
<script setup lang="ts" name="component-PiniaBasicSetup">
import { storeToRefs } from 'pinia';
import { useCounterStoreForSetup } from '@/store/counterForSetup';
// setup composition API模式
const counterStoreForSetup = useCounterStoreForSetup();
const { count } = storeToRefs(counterStoreForSetup);
const { increment, doubleCount } = counterStoreForSetup;
</script>
<template>
<div class="box-styl">
<h2>Setup模式</h2>
<p class="section-box">
Pinia的state: count = <b>{{ count }}</b>
</p>
<p class="section-box">
Pinia的getters: doubleCount() = <b>{{ doubleCount() }}</b>
</p>
<div class="section-box">
<p>Pinia的action: increment()</p>
<button @click="increment">点我</button>
</div>
</div>
</template>
<style lang="less" scoped>
.box-styl {
margin: 10px;
.section-box {
margin: 20px auto;
width: 300px;
background-color: #d7ffed;
border: 1px solid #000;
}
}
</style>
- Pinia在setup模式下的调用机制是先install再调用。
- install这样写:
const counterStoreForSetup = useCounterStoreForSetup();,其中useCounterStoreForSetup就是你定义store的变量; - 调用就直接用
counterStoreForSetup.xxx(xxx包括:state、getters、action)就好。 - 代码中获取state是用了解构赋值,为了保持state的响应式特性,需要用
storeToRefs进行包裹。
兼容Vue2的Options API调用方式
<script lang="ts">
import { mapState, mapActions } from 'pinia';
import { useCounterStoreForOption } from '@/store/counterForOptions';
// setup composition API模式
export default {
name: 'component-PiniaBasicOptions',
computed: {
// 获取state和getters
...mapState(useCounterStoreForOption, ['count', 'doubleCount'])
},
methods: {
// 获取action
...mapActions(useCounterStoreForOption, {
increment: 'increment',
reset: 'reset'
})
}
};
</script>
<template>
<div class="box-styl">
<h2>Options模式</h2>
<p class="section-box">
Pinia的state: count = <b>{{ this.count }}</b>
</p>
<p class="section-box">
Pinia的getters: doubleCount() = <b>{{ this.doubleCount }}</b>
</p>
<div class="section-box">
<p>Pinia的action: increment()</p>
<button @click="this.increment">点我</button>
</div>
<div class="section-box">
<p>Pinia的action: reset()</p>
<button @click="this.reset">点我</button>
</div>
</div>
</template>
<style lang="less" scoped>
.box-styl {
margin: 10px;
.section-box {
margin: 20px auto;
width: 300px;
background-color: #d7ffed;
border: 1px solid #000;
}
}
</style>
5. 良好的编程习惯
state的改变交给action去处理: 上面例子,counterStoreForSetup有个pinia实例属性叫$state是可以直接改变state的值,但不建议怎么做。一是难维护,在组件繁多情况下,一处隐蔽state更改,整个开发组帮你排查;二是破坏store封装,难以移植到其他地方。所以,为了你的声誉和安全着想,请停止游离之外的coding
