Pinia
Pinia 最初是在 2019 年 11 月左右重新设计使用 Composition API。从那时起,最初的原则仍然相同,但 Pinia 对 Vue 2 和 Vue 3 都有效,并且不需要您使用组合 API。除了安装和 SSR 之外,两者的 API 都是相同的,并且这些文档针对 Vue 3,并在必要时提供有关 Vue 2 的注释,以便 Vue 2 和 Vue 3 用户可以阅读!
为什么要使用 Pinia?
Pinia 是 Vue 的存储库,它允许您跨组件/页面共享状态。如果您熟悉 Composition API,您可能会认为您已经可以通过一个简单的 export const state = reactive({}). 这对于单页应用程序来说是正确的,但如果它是服务器端呈现的,会使您的应用程序暴露于安全漏洞。 但即使在小型单页应用程序中,您也可以从使用 Pinia 中获得很多好处:
- dev-tools 支持
- 跟踪动作、突变的时间线
- Store 出现在使用它们的组件中
- time travel 和 更容易的调试
- 热模块更换
- 在不重新加载页面的情况下修改您的 Store
- 在开发时保持任何现有状态
- 插件:使用插件扩展 Pinia 功能
- 为 JS 用户提供适当的 TypeScript 支持或 autocompletion
- 服务器端渲染支持
pinia 核心概念
- state: 状态
- getters: 计算属性
- actions: 修改状态(包括同步和异步,pinia 中没有 mutations)
基本使用
1、安装
npm i pinia
2、在 main.js
中挂载 pinia
import { createApp } from "vue";
import { createPinia } from "pinia";
import App from "./App.vue";
const app = createApp(App);
app.use(createPinia());
app.mount("#app");
组合式 Store
使用 setup 模式定义,符合 Vue3 setup 的编程模式,让结构更加扁平化,推荐推荐使用这种方式。
新建文件store/counter.js
import { ref, computed } from "vue";
import { defineStore } from "pinia";
export const useCounterStore = defineStore("counter", () => {
// state
const count = ref(100);
// getters
const doubleCount = computed(() => count.value * 2);
// mutations
const increment = () => count.value++;
// actions
const incrementAsync = () => {
setTimeout(() => {
count.value++;
}, 1000);
};
return { count, doubleCount, increment, incrementAsync };
});
在 App.vue
中使用
<script setup>
import { useCounterStore } from "./stores/counter.js";
const counter = useCounterStore();
</script>
<template>
<div>
<h1>根组件 --- {{ counter.count }}</h1>
<h3>{{ counter.doubleCount }}</h3>
<button @click="counter.increment">加 1</button>
<button @click="counter.incrementAsync">异步加 1</button>
</div>
</template>
选项式 Store(演示)
使用 options API 模式定义,这种方式和 vue2 的组件模型形式类似,也是对 vue2 技术栈开发者较为友好的编程模式。但是这种技术在 vue3 中几乎不用了,稍微看看即可。
state 的使用
新建文件 store/counter_options.js
import { defineStore } from "pinia";
// 创建 store,命名规则:useXxxStore
// 参数 1:store_id 的唯一表示
// 参数 2:对象,可以提供 state actions getters
export const useCounterStore = defineStore("counter", {
state: () => {
return {
count: 0,
};
},
});
getters 的使用
1、在 getters 中提供计算属性,如果需要修改请写在 actions
import { defineStore } from "pinia";
export const useCounterStore = defineStore("counter", {
state: () => {
return {
count: 0,
};
},
getters: {
doubleCount() {
return this.count * 2;
},
},
});
2、在 App.vue
组件中使用
<template>
<div>
<h1>根组件 --- {{ counter.count }}</h1>
<h3>{{ counter.doubleCount }}</h3>
</div>
</template>
actions 的使用
在 pinia 中没有 mutations,只有 actions,不管是同步还是异步的代码,都可以在 actions 中完成。
1、在 actions 中提供方法并且修改数据
import { defineStore } from "pinia";
export const useCounterStore = defineStore("counter", {
state: () => {
return {
count: 0,
};
},
getters: {
doubleCount() {
return this.count * 2;
},
},
actions: {
increment() {
this.count++;
},
incrementAsync() {
setTimeout(() => {
this.count++;
}, 1000);
},
},
});
2、在 App.vue
组件中使用
<script setup>
// import {useCounterStore} from "./stores/counter.js";
import { useCounterStore } from "./stores/counter_options.js";
const counter = useCounterStore();
console.log(counter.count);
console.log(counter.count);
</script>
<template>
<div>
<h1>根组件 --- {{ counter.count }}</h1>
<h3>{{ counter.doubleCount }}</h3>
<button @click="counter.increment">加 1</button>
<button @click="counter.incrementAsync">异步加 1</button>
</div>
</template>
对比总结
上面 2 种定义方式效果都是一样的,我们用 defineStore
方法定义一个 store
,里面分别定义 1 个 count
的 state
,1 个 increment
的 action 和 1 个 doubleCount
的 getters
。其中 state 是要共享的全局状态,而 action 则是让业务方调用来改变 state 的入口,getters
是获取 state 的计算结果。
使用方式是通过 const useXxxStore = defineStore('id',函数)
创建 store
得到使用的对象
选项式 | 组合式 |
---|---|
state | ref 和 reactive 创建的响应式数据 |
getter | computed 创建的计算属性 |
actions | 普通函数,异步函数都可以 |
storeToRefs 辅助函数
如果直接从 pinia 中解构数据,会丢失响应式,使用 storeToRefs 可以保证解构出来的数据也是响应式的
<script setup>
import { useCounterCompositionStore } from "./store/counter.js";
import { storeToRefs } from "pinia";
const counter_composition = useCounterCompositionStore();
// 如果直接从 pinia 中解构数据,会丢失响应式
// const {count, doubleCount} = counter_composition
// 使用 storeToRefs 可以保证解构出来的数据也是响应式的
const { count, doubleCount } = storeToRefs(counter_composition);
</script>