持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第27天,点击查看活动详情
简介
前面笔者在vuex4
中应用了TypeScript
,总体来说还行,但是仍然有配置麻烦,不支持getters、mutations、actions
等痛点。今天我们再来实战下官方推荐的新的vue
状态管理工具pinia
。
pinia
由 vue
团队中成员所开发的,因此也被认为是下一代的 vuex
,即 vuex5.x
,在 vue3
的项目中使用也是备受推崇。所以 pinia
的学习迫在眉睫。
下面我们正式开始pinia
的学习吧。
安装
yarn add pinia
// or
npm install pinia
创建pinia并传递给vue实例
创建一个 pinia
并传递给 vue
应用。
在这一步我们可以发现,我们只需要简单创建一个pinia
对象传给vue
实例就可以自动收集我们后续创建的新store
。并不需要像vuex
一样把各个模块聚集在根store
里面传递给vue
实例。总体来说使用更加简单。
import { createPinia } from 'pinia'
import { createApp } from 'vue'
import App from './app.vue'
createApp(App).use(createPinia()).mount('#app')
创建store
与vuex
不同,pinia
没有了根store
和modules
的概念。在pinia
中一个store
一个文件,一个store
里面包含state、getters、actions
三部分。总体来说更简单更清晰。
需要使用defineStore
来创建store
。
注意第一个参数一定要唯一。
import { defineStore } from "pinia";
export const mainStore = defineStore("main", {
state: () => {
return {
name: "randy man",
age: 24,
};
},
getters: {
getterName(state) {
return state.name.toUpperCase() + "-----" + state.age;
},
// 这种方式也可以,不过需要自己手动指定返回值类型
getterAge(): number {
return this.age;
},
},
actions: {
// 直接使用this修改state
changeName(payload: string) {
this.name = payload;
return "success";
},
// 异步action
async fetchData(payload: number) {
const response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${payload}`
);
const res = response.json();
// 调用其他action
// this.changeName("xxx");
return res;
},
},
});
下面我们详细介绍下第二个参数。这个参数里面包含state,getters,action三个属性。
state
state
就是状态,和vuex
一样,就不过多介绍了。只需要注意他是一个方法,返回一个state
对象。
state: () => {
return {
name: "randy man",
age: 24,
};
},
getters
getters
是计算属性,和vuex
中是一样的。但是需要知道它支持两种写法。
通过this
修改state
的方式需要自己手动指定返回值类型。
getters: {
getterName(state) {
return state.name.toUpperCase() + "-----" + state.age;
},
// 这种方式也可以,不过需要自己手动指定返回值类型
getterAge(): number {
return this.age;
},
},
actions
在pinia
中没有了mutations
,所以同步和异步操作都在action
中进行。
并且在pinia
中是支持直接调用别的action
,或者别的模块的action
。
actions: {
// 直接使用this修改state
changeName(payload: string) {
this.name = payload;
return "success";
},
// 异步action
async fetchData(payload: number) {
const response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${payload}`
);
const res = response.json();
// 调用其他action
// this.changeName("xxx");
return res;
},
},
在 action
里调用其他 store
里的 action
也比较简单,引入对应的 store
后即可访问其内部的方法了,跟在vue
组件中使用是一样的。
// user.ts
import { useAppStore } from './app'
export const useUserStore = defineStore({
id: 'user',
actions: {
async login(account, pwd) {
const { data } = await api.login(account, pwd)
const appStore = useAppStore()
appStore.setData(data) // 调用 app store 里的 action 方法
return data
}
}
})
在vue组件使用
在vue
组件中使用也很简单,需要使用哪个store
引入哪个store
就可以啦。
<template>
<div>
<div>name: {{ name }}</div>
<div>getterName: {{ getterName }}</div>
<div>
<button @click="updateMainName">修改main name</button>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, computed } from "vue";
import { mainStore } from "@/pinia/main";
import { storeToRefs } from "pinia";
export default defineComponent({
name: "PiniaView",
setup() {
const store = mainStore();
const updateMainName = async () => {
// 直接修改 不建议
// store.name = "demi woman";
// 对象形式修改
// store.$patch({ name: "demi woman" });
// 函数形式修改
// store.$patch((state) => {
// state.name = "demi woman";
// });
// 通过action修改
const result = store.changeName("demi woman");
console.log(result);
// 返回的值不会被Promise包裹
const result2 = await store.fetchData(1);
console.log(result2);
};
// 直接解构不能响应式
// const { name, getterName } = store;
// 这样响应式
const name = computed(() => store.name);
const getterName = computed(() => store.getterName);
// 这样也能响应式
// const { name, getterName } = storeToRefs(store);
// 或者直接传递store过去使用也是能响应式的
// store
return {
name,
getterName,
updateMainName,
};
},
});
</script>
跟vuex
相比,使用pinia
不需要定义state
类型,不用定义key
就能得到TypeScript
的优势。
可以看到我们使用store
的时候,它的所有属性和方法state、getters、actions
等都会自动提示出来,并且当我们使用了不存在的属性在编码期间会直接报错。
获取state
获取state
的方式有三种,但是需要注意通过直接解构的方式数据是不会响应式的。如果需要响应式需要使用computed
或storeToRefs
import { storeToRefs } from "pinia";
const store = mainStore();
// 直接解构不能响应式
// const { name } = store;
// 这样响应式
const name = computed(() => store.name);
// 这样也能响应式
// const { name } = storeToRefs(store);
获取getters
获取getters
的方式也有三种,但是需要注意通过直接解构的方式数据是不会响应式的。如果需要响应式需要使用computed
或storeToRefs
import { storeToRefs } from "pinia";
const store = mainStore();
// 直接解构不能响应式
// const { getterName } = store;
// 这样响应式
const getterName = computed(() => store.getterName);
// 这样也能响应式
// const { getterName } = storeToRefs(store);
修改state
修改state
的方式也有很多种,可以直接修改、还可以使用$patch
来传递对象或方法来修改、还可以通过action
修改。
const updateMainName = async () => {
// 直接修改 不建议
// store.name = "demi woman";
// 对象形式修改
// store.$patch({ name: "demi woman" });
// 函数形式修改
// store.$patch((state) => {
// state.name = "demi woman";
// });
// 通过action修改
const result = store.changeName("demi woman");
// action 返回啥就是啥 result等于'success'
};
虽然修改方式很多,但是还是在实际开发中推荐使用action
来修改state
。
使用action
的时候还有一点需要注意。我们知道在vuex
中,action
如果有返回值是会被Promise
包裹resolve
出来,也就是会返回一个Promise
对象。但是在pinia
中,action
返回啥就是啥不会被Promise
包裹resolve
出来。
监听
在pinia
中我们可以通过xxxstore.$subscribe((mutation, state) => {})
来监听state
的变化。
通过xxxstore.$onAction(({ after, onError, name, store, args }) => {})
来监听action
和它们的结果的变化。
$subscribe
默认组件销毁监听器消失,加上 { detached: true }
参数后 监听器不消失。
// 监听state的变化
// 默认组件销毁监听器消失,加上 { detached: true } 参数后 监听器不消失
userStore.$subscribe(
(mutation, state) => {
console.log("mutation", mutation);
console.log("state", state);
},
{ detached: true }
);
$onAction
添加第二个参数 true
,此订阅器即便在组件卸载之后仍会被保留。
// 添加第二个参数true 此订阅器即便在组件卸载之后仍会被保留
// 监听action的变化
userStore.$onAction(({ after, onError, name, store, args }) => {
console.log(` Start "${name}" with params [${args.join(", ")}].`, store);
// 这将在 action 成功并完全运行后触发。
// 它等待着任何返回的 promise
after((result) => {
console.log(`after Finished ${result}`);
});
// 如果 action 抛出或返回一个拒绝的 promise,这将触发
onError((error) => {
console.warn(`onError Failed ${error}.`);
});
}, true);
数据持久化
我们知道,vuex
和pinia
本质上都是对象,都是临时存储,当页面刷新数据都会丢失。
想要刷新不丢失就需要我们手动存储在localStorage
或sessionStorage
里面。如果不想手动处理,我们也可以使用插件。
vuex
使用vuex-persistedstate
pinia
使用pinia-plugin-persistedstate
具体怎么使用笔者就不再赘述了大家可以自行查看文档,原理都是一样的,通过配置将指定数据存储到localStorage
或sessionStorage
里面实现数据持久化。
总结
-
pinia
有完整的typescript
的支持。不再像使用vuex
需要定义key
、定义state
类型那么麻烦。 -
足够轻量,压缩后的体积只有
1.6kb
。 -
去除
mutations
,只有state,getters,actions
。跟react-redux
类似,同步异步都在action
里面进行。根简单清晰。 -
没有模块嵌套,只有
store
的概念,store
之间可以自由使用,更好的代码分割。更简单易懂。 -
无需手动添加
store
,store
一旦创建便会自动添加,我们只需要在vue
组件直接引用需要的store
使用即可。
系列文章
TypeScript学习之类型推断、类型断言、双重断言、非空断言、确定赋值断言、类型守卫、类型别名
后记
感谢小伙伴们的耐心观看,本文为笔者个人学习笔记,如有谬误,还请告知,万分感谢!如果本文对你有所帮助,还请点个关注点个赞~,您的支持是笔者不断更新的动力!