前言
作为vue的公共状态管理库,已经有了青梅竹马vuex,为什么还会再出现一个天降之物pinia呢?虽然现在vuex还没有被淘汰,vue现在左拥右抱两大公共状态管理库,但青梅竹马总是敌不过天降之物呀
pinia对比vuex有什么优势呢?为什么要选择pinia
以下是官方给出的几大优势:
除了这几点对比vuex还有就是体积小,pinia体积只有1kb左右,娇小可爱谁不喜欢呢?其次易学好上手,简单的api大大降低了上手难度!
学习pinia
首先安装pinia
js
yarn add pinia
// 或者使用 npm
npm install pinia
定义pinia
js
//main.js
//只需要简单的注册以下即可
import { createApp } from "vue";
import App from "./App.vue";
import { createPinia } from "pinia";
createApp(App).use(createPinia()).mount("#app");
//store.js
//在这里只需要做一件事就可以
//从pinia中引入defineStore 然后再将defineStore的实例导出去即可
import { defineStore } from "pinia";
//defineStore有三个参数
//第一个参数是公共状态的id,也是唯一的
//第二个参数有两种,一种为对象,Options API形式,另一种为回调函,Composition API形式
//第三个参数是在pinia引入插件的时候所使用的,一般情况用不到
export const useStore = defineStore("store", {
//...
});
Options API写法
使用pinia
定义store
ts
//store.ts
import { defineStore } from "pinia";
export const useStore = defineStore("store", {
//这里存放的就是公共状态,在pinia里不用害怕使用箭头函数会找不到store的实例
state: () => ({
name: "张三",
}),
//这里为计算值,接收一个state作为参数,他是惰性的,只有状态改变时他才会运行
//请注意,在执行此操作时,getter 不再缓存,它们只是您调用的函数。
//但是,您可以在 getter 本身内部缓存一些结果,这并不常见,但应该证明性能更高
getters: {
updataName: (state) => state.name + "是个人",
//访问内部状态,也可以使用this获取到,即使是使用的箭头函数
isHeight() {
return this.updataName + "ta的身高是一米八";
},
},
//在这里定义一些方法来改变sotre内部的状态
actions: {
//方法所接收的参数为传进来的参数
setName(name: string) {
this.name = name;
},
// 异步操作
//因为没有mutation,这里既可以执行同步任务,也可以执行异步任务
//使我们写公共状态的心理负担又下降了许多
async awaitSetData(value: string) {
setTimeout(() => {
this.name = value;
}, 2000);
},
},
});
在Options API
写法的组件中调用
js
//app.vue
<template>
<div>
<button @click="setName('李四')">改变名字</button>
<button @click="set">改变名字</button>
<button @click="awaitSetData('李四')">异步改变名字</button>
<!-- 因为store是被useStore()函数返回的 因此可以像这样直接使用store.updataName -->
<h1>{{ updataName }}</h1>
<h1>{{ store.updataName }}</h1>
<!-- 像这两种写法,得到的结果也是一致的 -->
<h1>{{ name }}</h1>
<h1>{{ store.name }}</h1>
</div>
</template>
<script lang="ts">
//从pinia文件中引入 导出的实例方法
import { useStore } from "./store/home";
//这里pinia提供了与vuex的相似的api 这些辅助函数的使用方法也是与vuex类似的
import { mapState, mapActions } from "pinia";
export default {
data() {
return {
//这里实例方法所得到的就是那个文件定义的store
//这里可以理解为vuex中的store
store: useStore(),
};
},
computed: {
//辅助函数也是同vuex一样有着两种写法,看个人喜好使用,gatter也是使用了mapState
//但与之不同的是他有两个参数,第一个是导出store实例方法,第二个才是状态
...mapState(useStore, ["updataName", "name"]),
...mapState(useStore, {
n: "name",
upN: "updataName",
}),
},
methods: {
//方法定义,一样的似曾相识,有两种,选择其中一种使用即可,也是两个参数,第一个是导入的实例方法
...mapActions(useStore, ["setName", "awaitSetData"]),
...mapActions(useStore, {
sName: "setName",
awaitName: "awaitSetData",
}),
set() {
//这里的store是data中的store,但是被赋值useState的实例,所以有着store的方法
this.store.setName("李四");
},
},
};
</script>
这样使用Options API
写法,pinia
的定义和使用就完成了
Composition API
使用Composition API
定义,比Options API
写法简单许多
ts
//store.ts
import { defineStore } from "pinia";
import { ref, reactive, computed } from "vue";
export const useStore = defineStore("store", () => {
//只需要使用vue的ref或reactive定义响应数,再retrun出去,就算定义好状态了
//可以使用类似于Options API写法一样,将所有的状态存放到一个对象中,调用的时候直接.属性值就可以了
const state = reactive({
userInfo: {},
});
//单独定义一个状态
const name = ref("张三");
//getter是使用computed计算属性来实现了,计算属性还会缓存,比getter又多了点东西
const updateName = computed(() => {
return name.value + "是个人";
});
//当然也可以使用可传参的计算属性
const updateNameTow = computed(() => (v: string) => {
return name.value + v;
});
// 定义方法直接写fn函数就可以了
const setName = (v: string) => {
name.value = v;
};
//这里也可以是异步的
const setNameAsync = async (v: string) => {
await new Promise((resolve) => {
setTimeout(() => {
name.value = v;
resolve(v);
}, 1000);
});
};
//唯一不好的就是所有的状态或方法都需要return出去,内部就相当于一个工厂函数,处理完之后将处理好的数据返回出去
return {
state,
name,
updateName,
updateNameTow,
setName,
setNameAsync,
};
});
这样看是不是比Options API
写法更清晰一些,写的状态也没必要再上下翻着看了
Composition API
方法使用也是比较简单的
js
//app.vue
<template>
<div>
<!-- 一切都于预想的一样正常 -->
<button @click="set">改变名字</button>
<h1>{{ store.name }}</h1>
<!-- 解构后的方法使用,是不是Options API写法中使用了跟辅助函数类似呢 -->
<button @click="setName('李四')">改变名字</button>
<!-- 解构出来的值也是响应的,正常使用 -->
<h1>{{ name }}</h1>
<h2>{{ newName }}</h2>
<!-- 这里渲染出来就是 store.name + 一米八 -->
<h1>{{ updateNameTow("一米八") }}</h1>
<h1>{{ updateName }}</h1>
</div>
</template>
<script setup lang="ts">
//引入之后直接定义赋值给一个实例,就可以正常使用了
import { useStore } from "../store/home";
//定义store实例
const store = useStore();
const set = () => {
//实例中的方法随便调用
store.setName("李四");
};
/* 在这里方法是可以使用es6解构赋值,直接解构的,解构后一样的可以正常执行 */
const { setName } = store;
//或者 省了一步定义实例的操作
//const { setName } = useStore()
//既然方法可解构,状态是不是也能解构
//可以但不能直接解构,否者会失去响应性,pinia中提供了一个storeToRefs来解决了这个问题
//这样解构出来的状态就是响应式的
const { name } = storeToRefs(store); //也可以是storeToRefs(useStore())
//不仅可以使用pinia提供的方法 ,也可以使用vue提供的toRefs方法
//解构赋值的重命名应该都知道吧
const { name: newName } = toRefs(store); //也可以是toRefs(useStore())
//计算属性也是一样的需要使用方法来进行解构,但是可传值的计算数据类似于方法,所以可以直接解构
//这里updateNameTow是响应的,updateName却不是
const { updateNameTow, updateName } = store;
</script>
看着是不是很简单,写的情况比较多,所以显得多一些,实际上使用只需要一到两行代码就可以使用pinia的store了
上面看我在使用vue的api和pinia的api的时候是没有引入的,是因为我用了插件,正常写的时候别忘了引入api ,也可以使用插件,看我另一篇:在vite中,如何让vue项目优雅的自动引入组件 - 掘金 (juejin.cn)
pinia实例上的一些方法
当我们打印了pinia的store实例之后会发现,会有一些它内部自带的方法
这些方法的作用都是哪些
$dispose
$dispose
是一个用于清理 store 的方法。当您不再需要某个 store 或者在组件卸载时,可以调用 $dispose
方法来释放该 store 所占用的资源,以避免内存泄漏。
js
const store = useStore();
// 在组件卸载时清理 store
beforeUnmount(() => {
store.$dispose();
});
$patch
$patch
是一个用于更新 store 状态的方法。它允许您以更细粒度的方式更新状态,而不需要完全替换整个状态对象。
$patch
方法接受一个对象作为参数,该对象包含要更新的状态属性和对应的值。只有指定的属性会被更新,其他状态属性保持不变。
js
//store.ts
//需要传入一个对象,对应相应的变量名即可
export const useStore = defineStore("store", () => {
const state = reactive({
userInfo: {},
});
const name = ref("张三");
return {
state,
name,
};
//app.vue
const store = useStore();
//这两种都是可以的 执行后会立马改变
store.$patch({ name: "李四" });
store.$patch({ state: { userInfo: { name: "李四" } } });
$reset
这个方法很简单,$reset
是一个用于重置 store 的方法。它将 store 的状态、getter、action 和订阅全部重置为初始状态。
js
const store = useStore();
//改变了公共状态的值
//...
//调用$reset方法,之后会全部重置如初
store.$reset()
$subscribe
$subscribe
是一个用于订阅 store 变化的方法。它允许您监听 store 的状态变化,并在状态发生变化时执行相应的逻辑。
$subscribe
方法接受一个回调函数作为参数,该回调函数将在 store 的状态发生变化时被触发。回调函数接收两个参数:新的状态对象和一个包含变化详情的对象。
js
//知道状态什么时候改变了,改变的同时做一些操作
const store = useStore();
//...
//状态改变时会调用这个方法
store.$subscribe((n, s) => {
console.log(n, s);
});
$onActions
这个时用于订阅actions的一个方法,接收两个参数,第一个是回调函数,执行一些操作,第二个为布尔值,当为ture的时候,当组件卸载时不会自动卸载当前订阅
js
const store = useStore()
const unsubscribe = store.$onAction(
({
name, // 方法的名字
store, // store 实例
args, // 调用这个 action 的参数
after, // 在这个 action 执行完毕之后,执行这个函数
onError, // 在这个 action 抛出异常的时候,执行这个函数
}) => {
//方法改变时做一些操作,异步的时候是一个不错的选择
//...
after(() => {
//方法执行完毕后做一些操作
});
onError(() => {
//方法执行失败后做一些操作
});
}
);
// 手动移除订阅
unsubscribe();
pinia插件
这里pinia插件使用方法与vue插件使用方法类似,就不过多说明了,感兴趣的可以搜索相关资料
js
import { createPinia } from 'pinia'
// 为安装此插件后创建的每个store添加一个名为 `secret` 的属性
// 这可能在不同的文件中
function SecretPiniaPlugin() {
return { secret: 'the cake is a lie' }
}
const pinia = createPinia()
// 将插件提供给 pinia
pinia.use(SecretPiniaPlugin)
// 在另一个文件中
const store = useStore()
store.secret // 'the cake is a lie'
服务端渲染
前面也说过,pinia的优势也在于服务端渲染,只要在 setup
函数、getters
和 actions
的顶部调用 useStore()
函数,使用 Pinia 创建store应该可以立即用于 SSR,也就是vue3中正常写就应用于SSR了,在setup之外或者js文件中使用
js
const pinia = createPinia()
const app = createApp(App)
app.use(router)
app.use(pinia)
router.beforeEach((to) => {
// 当前运行的应用
const main = useMainStore(pinia)
if (to.meta.requiresAuth && !main.isLoggedIn) return '/login'
})
结尾
pinia同vuex一样,是单向数据流的,在vue文件内是不能更改状态的,另外介绍这么久pinia没有发现有module这一块的内容,是因为pinia可以创建多个文件,多个store,每一个文件就是一个模块,使用时如果需要多个模块的store,直接分分文件写就行了,相互之间也并不冲突!
pinia作为vue天降之物的公共状态管理库,确实有着很多突出的点,学习起来也比较简单,如果是你,是选择pinia还是vuex?