快速了解 Pinia (上)基础篇
前言
Pinia是 Vue 生态的状态管理工具,由 Vue 官方团队成员打造,是事实上的Vuex 5。
Pinia最大的优点就是简单。以一个个独立的Store 为核心,摒弃了Vuex复杂的Mutations、同步和异步统一合并到Actions中等设计,配合优秀的Devtools,使用体验提升巨大。
本文分上下两篇,首先谈谈基本使用,接下来通过一个购物车案例对它有一个比较生动的认识。请配合 Pinia 官方文档 阅读。
一、初始化项目
本例采用Vite,在目录下新建一个Vite项目:
yarn cerate vite
一路选下去(记得是vue-ts),然后在新项目内安装Pinia依赖:
yarn add pinia
最后安装项目全部依赖,便可开始编写工作:
yarn install
二、将 Pinia 实例 挂载到 Vue 应用
在工作区下的main.ts,导包后创建实例,并用.use()挂载到当前App上:
// main.ts
import { createApp } from "vue";
import { createPinia } from "pinia"; // +
import "./style.css";
import App from "./App.vue";
const app = createApp(App);
//创建 pinia 实例 // +
const pinia = createPinia(); // +
app.use(pinia); // +
app.mount("#app");
三、创建Store
在工作区目录下新建 Store/,用来放置所有的Store。 推荐一个 .ts 文件内只放一个Store。
使用defineStore()创建并返回一个Store实例,接收两个参数
name:string `Store`的唯一 id ,Pinia 将用它来连接 `store` 和 `devtools`。options:object 一个对象,内部规定了 state / acions / getters
state类似 Vue 组件的data,本质是一个箭头函数。数据要定义在箭头函数的return {...}内。- 必须是函数:避免在服务端渲染时,交叉请求导致的数据状态污染
- 必须是箭头函数:为了 TS 更好的类型推导
getters类似 Vue 组件的computed,本质是一个对象。内部的函数如果想获取state内部值,需要声明参数stateactions类似 Vue 组件的methods,本质是一个对象。内部的函数如果想获state内部值,需要使用this
最后记得把这个Pinia实例导出去
实例名一般为
useXxxxStoredefineStore()的name命名为xxxx
// src/store/index.ts
import { defineStore } from "pinia";
export const useMainStore = defineStore("main", {
state: () => {
return {
count: 100,
foo:"bar",
arr:[1,2,3],
};
},
getters: {
// 参数 state 是可选的
count10(state):number{
console.log("getters !");
return state.count+10;
}
},
//action 不可用箭头函数定义
actions: {
changeState(num:number){
this.count+=num;
this.foo="fixed via Store-action"
this.arr.push(8)
}
},
});
四、使用 store
1. 组件中导入store并实例化
// src/componet/HelloWorld.vue
import { useMainStore } from "../store/index";
import { storeToRefs } from "pinia";
//获得 Store 实例
const mainStore = useMainStore();
如果希望从store引用的数据是响应式的,即在本组件修改的state后能实时更新到store里,还需导入storeToRefs
2.响应式数据
出于响应式数据的目的,这样使用数据是错的:
const { count, foo } = mainStore;
因为这只是复制了一份数据,修改后并不能更新store状态。
正确用法是将解构出来的数据做 ref 响应式处理,如下:
const { count, foo, arr } = storeToRefs(mainStore);
3.在组件中访问、修改 state
- 方式一:直接访问 / 修改单个数据
const mainStore = useStore()
mainStore.count++
mainStore.foo = "hello"
mainStore.$reset() // 重置回初始值
- 方式二:通过
$patch()批量修改多个数据,并不是写法上的简便,而是为了性能上的优化
mainStore.$patch({
count: mainStore.count + 1,
foo: "hello",
arr: [...mainStore.arr, 4], // 对于 array 等复杂结构,这样写法太低效
})
但是上面这种做法也是有问题的:虽然可以通过直接赋值修改状态,但是对于像 Array 等数据类型,无法获取该对象,这意味着所有的类方法都不能使用。
- 方式三:
$patch()参数内改用回调函数:
mainStore.$patch((state) => {
state.count++;
state.foo = "hello";
state.arr.push(5); // 可以调用方法了
});
- 方式四:当然了,逻辑再复杂的话,直接调用
store的acions定义的函数即可。
// src/store/index.ts
export const useMainStore = defineStore("main", {
...
actions: {
changeState(num:number){
this.count+=num;
this.foo="fixed via Store-actions"
this.arr.push(8)
}
},
});
// src/componet/HelloWorld.vue
<template>
<div>{{mainStore.count}}</div>
<div>{{mainStore.foo}}</div>
<hr />
<div>{{mainStore.count10}} 第1次调用 getters,调用count10()计算count+10</div>
<div>{{mainStore.count10}} 第2次调用 getters,属性值不变,不再调用count10()计算</div>
<div>{{mainStore.count10}} 第3次调用 getters,属性值不变,不再调用count10()计算</div>
<p>
<button @click="handleChangeCount">修改count</button>
</p>
</template>
<script lang="ts" setup>
import { useMainStore } from "../store"; // 文件路径默认为 index ,不写也行
import { storeToRefs } from "pinia";
//获得 Store 实例
const mainStore = useMainStore();
const { count, foo, arr } = storeToRefs(mainStore);
const handleChangeCount = () => {
//方式4:数据操作比较多时,可在 actions 封装函数,然后在普通组件中调用这些现成的函数
mainStore.changeState(10);
};
</script>
4. getters(computed) 的特性
getters返回state中某个变量进行特定计算后的结果,这个结果是一个缓存值。如果该变量状态没发生变化,那么
getters无论调用多少次,值都不变。
本篇介绍了Pinia的基础使用,下篇将以一个购物车案例,将会有更生动的了解。
转载自:https://juejin.cn/post/7160934086314295303