前言:参考 Pinia 中文文档,在 Vue3 配合 ts 中的使用。
Pinia 是 Vue 的存储库,允许跨组件/页面共享状态。
热模块更换、保持任何现有状态、使用插件扩展 Pinia 功能、TS 支持、服务端渲染支持。
Pinia 提供更简单的 API,具有更少的规范,mutations 不再存在。提供了 Composition-API 风格的 API,与 TS 使用时有可靠的类型推断支持。
yarn add pinia
# or
npm install pinia
在 main.ts 中注册 pinia:
// main.ts
import { createApp } from "vue";
import App from "./App.vue";
import { createPinia } from "pinia";
const app = createApp(App);
const pinia = createPinia();
app.use(pinia);
app.mount("#app");
托管全局状态。每个组件都可以读取和写入。它有三个概念,state、getters 和 actions 等同于组件中的“数据”、“计算”和“方法”。
需要保存跨组件使用的相同状态。例如导航栏中显示的用户信息。
使用 defineStore() 定义的,需要一个唯一名称,作为第一个参数传递,将返回的函数命名为 use...:
// @/store/demo.ts
import { defineStore } from "pinia";
export const useDemoStore = defineStore("demo", {
state: () => {
return {
name: "yq",
age: 18,
loves: ["book"],
};
},
actions: {
changeName() {
this.name = "yqcoder";
},
},
});
在组件中使用 useDemoStore 创建实例,在实例上可以直接访问 state,getter,actions 定义的属性:
{{ demoStore.name }}
change name
store 是用 reavtive 包裹的对象,可以不用.value,但也不能解构:
{{ name }}
change name
使用 storeToRefs() 使解构的状态变响应:
{{ name }}
change name
state 是 store 的核心部分。
import { defineStore } from "pinia";
export const useDemoStore = defineStore("demo", {
state: () => {
return {
name: "yq",
age: 18,
loves: ["book"],
};
}
});
可以通过 store 实例直接访问和修改状态:
const { useDemoStore } from "@/store/demo.ts";
const demoStore = useDemoStore();
demoStore.name = 'yqcoder';
使用 $reset() 方法将状态重置为初始值:
import { useDemoStore } from "@/store/demo.ts";
const demoStore = useDemoStore();
demoStore.$reset();
使用 $patch 方法,可以同时修改多个状态:
import { useDemoStore } from "@/store/demo.ts";
const demoStore = useDemoStore();
demoStore.$patch({
name: "yyy",
age: 22,
});
并且 $patch 也接受一个函数来批量修改状态:
import { useDemoStore } from "@/store/demo.ts";
const demoStore = useDemoStore();
demoStore.$patch((state) => {
state.name = "yy";
state.age = 23;
state.loves.push("sex");
});
使用 $state 来替换 Store 的整个状态:
import { useDemoStore } from "@/store/demo.ts";
const demoStore = useDemoStore();
demoStore.$state = {
name: "yy",
age: 33,
loves: ["sex"],
};
Getter 等同于 Store 状态的计算值:
import { defineStore } from "pinia";
export const useDemoStore = defineStore("demo", {
state: () => {
return {
loves: ["book"],
};
},
getters: {
lovesL: (state) => state.loves.length,
},
});
通过 this 可以访问状态和 getter,在 TS 中,使用 this 访问状态,需要声明返回类型:
import { defineStore } from "pinia";
export const useDemoStore = defineStore("demo", {
state: () => {
return {
name: "yq",
loves: ["book"],
};
},
getters: {
nameL: (state) => state.name.length,
lovesL(): number {
return this.nameL;
},
},
});
Getters 只是 computed 属性,因此无法传递任何参数。 但可以通过一个函数来接受参数:
import { defineStore } from "pinia";
export const useDemoStore = defineStore("demo", {
state: () => {
return {
age: 18,
};
},
getters: {
yearAfter: (state) => {
return (yearNumber: number): number => state.age + yearNumber;
},
},
});
在组件中使用:
{{ deomStore.yearAfter(10) }}
Actions 相当于组件的 methods。 适合定义业务逻辑:
import { defineStore } from "pinia";
export const useDemoStore = defineStore("demo", {
state: () => {
return {
name: "yq"
};
},
actions: {
changeName() {
this.name = "yqcoder";
},
},
});
访问其他 store ,直接在内部使用它:
import { useAuthStore } from "./auth-store";
export const useDemoStore = defineStore("demo", {
state: () => ({
// ...
}),
actions: {
async fetchUserPreferences(preferences) {
const auth = useAuthStore();
if (auth.isAuthenticated) {
this.preferences = await fetchPreferences();
} else {
throw new Error("User must be authenticated");
}
},
},
});
使用 pinia.use() 将插件添加到 pinia 实例中:
import { createApp } from "vue";
import { createPinia } from "pinia";
// 为安装此插件后创建的每个store添加一个名为 `secret` 的属性
// 这可能在不同的文件中
function SecretPiniaPlugin() {
return { secret: "the cake is a lie" };
}
const pinia = createPinia();
// 将插件提供给 pinia
pinia.use(SecretPiniaPlugin);
const app = createApp(App);
app.use(pinia);
app.mount("#app");
// 在另一个文件中
const store = useStore();
store.secret; // 'the cake is a lie'
Pinia 插件是一个函数,返回要添加到 store 的属性。 有一个可选参 context:
export function myPiniaPlugin(context) {
context.pinia; // 使用 `createPinia()` 创建的 pinia
context.app; // 使用 `createApp()` 创建的当前应用程序(仅限 Vue 3)
context.store; // 插件正在扩充的 store
context.options; // 定义存储的选项对象传递给`defineStore()`
// ...
}
然后使用 pinia.use() 将此函数传递给 pinia:
pinia.use(myPiniaPlugin);
您可以通过简单地在插件中返回它们的对象来为每个 store 添加属性:
pinia.use(() => ({ hello: "world" }));
Pinia 插件参数类型检测:
import { PiniaPluginContext } from "pinia";
export function myPiniaPlugin(context: PiniaPluginContext) {
// ...
}
使用 app.use(pinia) 安装 pinia 插件后,任何 useStore() 调用都将起作用:
import { useUserStore } from "@/stores/user";
import { createApp } from "vue";
import App from "./App.vue";
// ❌ 失败,因为它是在创建 pinia 之前调用的
const userStore = useUserStore();
const pinia = createPinia();
const app = createApp(App);
app.use(pinia);
// ✅ 有效,因为 pinia 实例现在处于活动状态
const userStore = useUserStore();
使用 Vue Router 的导航守卫内部的 store 的例子:
import { createRouter } from "vue-router";
const router = createRouter({
// ...
});
// ❌ 根据导入的顺序,这将失败
const store = useStore();
router.beforeEach((to, from, next) => {
// 我们想在这里使用 store
if (store.isLoggedIn) next();
else next("/login");
});
router.beforeEach((to) => {
// ✅ 这将起作用,因为路由器在之后开始导航
// 路由已安装,pinia 也将安装
const store = useStore();
if (to.meta.requiresAuth && !store.isLoggedIn) return "/login";
});