可爱小菠萝🍍,安排一下!
内容基本参考官网:
https://pinia.vuejs.org
(官网的字体是Dank mono 不知道有没有跟我一样很喜欢的朋友😢 已经美美换上
内容
介绍
pinia:最新一代的轻量级状态管理插件
如果你之前使用过 vuex 进行状态管理的话,那么 pinia 就是一个类似的插件
优点:
- 完整的ts支持
- 轻量
- 去除了mutations,只有state、getters、actions
…
安装
npm i pinia
引入注册
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import {createPinia} from 'pinia'
const app = createApp(App);
const store = createPinia();
app.use(store);
初始化仓库Store
1.根目录下新建store
文件夹,再建一个index.ts
2.main.ts
store
是用defineStore()
定义的
第一个参数传一个唯一的名称
第二个参数还可以传一些别的比如getters、actions之类的,后面再说
import { defineStore } from "pinia";
export const useUserStore = defineStore('user', {
state: () => ({
// 初始化的值
name: 'AIpoem',
age: 20
}),
actions: {
changeInfo() {
this.name = 'change poem'
},
},
})
3.在组件中使用
实例化store
之后,可以直接在store
上访问任何定义在state
、getters
、actions
的属性
store
是经过reactive
包装的对象,这意味着我们不需要写.value
,但是就类似于props
,我们是不能解构store的
<script setup lang="ts">
import { useUserStore } from "./store";
const userStore = useUserStore();
// ❌❌❌ 这样会丢失响应性
const { name, age } = userStore;
</script>
<template>
{{ userStore.name }},{{ userStore.age }}
</template>
如果需要从store
中提出属性,同时保持其响应性,我们要用storeToRefs()
它会为每一个属性都包上一层ref
(会跳过action和任何不具有响应性的属性
并且,action
我们可以直接解构,它也是绑定在store身上的
<script setup lang="ts">
import { useUserStore } from "../store";
import { storeToRefs } from "pinia";
const userStore = useUserStore();
const { name, age } = storeToRefs(userStore);
const { changeInfo } = userStore;
</script>
State
访问State
可以通过store
实例直接对state
进行读和写
const userStore = useUserStore();
userStore.name = 'change poem';
重置State
可以调用store
实例上的$reset()
方法将state
重置为初始值
const userStore = useUserStore();
console.log(userStore.name); // AIpoem
userStore.name = "change poem";
userStore.$reset();
console.log(userStore.name); // AIpoem
改变State
除了用store
实例直接改变State
(userStore.name = "change poem"
),也可以调用$patch()
方法
使用$patch()
方法可以批量修改state
1.$patch()
传一个对象
允许你对state
进行部分更改
const userStore = useUserStore();
userStore.$patch({
name: "change poem",
})
2.$patch()
传一个函数
但是当state
中有数组的时候,上面那种传对象的方法就不太好用了,比如数组要新增、要删除一个元素,我们都要创建一个新数组来更新
那这种时候传函数就更方便
// 我上面的例子state中没有数组,这里就加一个数组演示一下
// state: () => ({
// // 初始化的值
// name: "AIpoem",
// age: 20,
// hobby: ["吃", "喝", "玩", "乐"],
// }),
const userStore = useUserStore();
// 这个state就是store实例上的state
userStore.$patch(state => {
state.hobby.push("睡")
})
// 传对象写法
// userStore.$patch({
// hobby: ["吃", "喝", "玩", "乐", "睡"]
// })
替换State
你可以给store
实例的$state
属性赋值一个新对象,这样能替换掉整个state
const userStore = useUserStore();
userStore.$state = {
name: "change poem",
age: 120,
hobby: ["吃", "喝", "玩", "乐", "睡"],
};
订阅状态
你可以通过store
实例的$subscribe()
方法来监视state
的变化
$subscribe()
有两个参数:
第一个参数是一个回调函数:
const userStore = useUserStore();
userStore.$subscribe((mutation, state) => {
// 可能值:‘direct’ 'patch object' 'patch function'
console.log(mutation.type) // 修改的方式
console.log(mutation.storeId) // defineStore()时传的名字
})
第二个参数是固定值: { detached: true }
当组件卸载
的时候,state
的订阅也会自动卸载,如果我们希望订阅在组件卸载后还能保留,那就传第二个参数
const userStore = useUserStore();
userStore.$subscribe((mutation, state) => {
...
}, { detached: true })
Getters
相当于store
实例的computed
,用来修饰值,并且是有缓存的
如果只需要用到state
,直接用就行
export const useUserStore = defineStore('user', {
state: () => ({
name: 'AIpoem',
}),
getters: {
sayName: state => `我的名字是${state.name}`
}
})
使用this
如果需要用到其他getter
,我们可以通过this
来访问store
实例
使用了this
的注意点:
1.用了this
就不能用箭头函数
,否则this
不指向store
实例
2.在ts中有必要定义返回值
的类型
export const useUserStore = defineStore('user', {
state: () => ({
// 初始化的值
name: 'AIpoem',
}),
getters: {
sayName: state => `我的名字是${state.name}`,
sayHi():string {
return `你好, ${this.sayName}`
}
}
})
模板中使用,就跟用computed
一样
<script setup lang="ts">
import { useUserStore } from "../store";
const userStore = useUserStore();
</script>
<template>
{{ userStore.sayName }} <br />
{{ userStore.sayHi }}
</template>
传参
getters
的传参方法也跟computed
一样,就是return
一个function
,用这个function
来接收参数
export const useUserStore = defineStore('user', {
state: () => ({
name: 'AIpoem',
}),
getters: {
isMyName: state => {
return function(userName: string):boolean {
return state.name === userName
}
}
// 简写
// isMyName: state => (userName: string) => state.name === userName
}
})
模板中直接传参就行:
<script setup lang="ts">
import { useUserStore } from "../store";
const userStore = useUserStore();
</script>
<template>
{{ userStore.isMyName("AIpoem") }} <br />
{{ userStore.isMyName("111") }}
</template>
⚠️:如果getters
返回了一个函数,它将不再缓存
Actions
相当于store
实例的methods
支持同步也支持异步
同步
import { defineStore } from "pinia";
interface User {
name: string,
age: number
}
export const useUserStore = defineStore('user', {
state: () => ({
userInfo: <User>{},
}),
actions: {
setUserInfo() {
this.userInfo = {
name: 'AIpoem',
age: 20
}
}
}
})
异步
一般结合async await一起使用
import { defineStore } from "pinia";
interface User {
name: string,
age: number
}
const getInfo = (): Promise<User> => {
return new Promise(resolve => {
setTimeout(() => {
resolve({
name: 'AIpoem',
age: 20,
})
}, 3000)
})
}
export const useUserStore = defineStore('user', {
state: () => ({
userInfo: <User>{},
}),
actions: {
async setUserInfo() {
this.userInfo = await getInfo()
}
}
})
订阅actions
可以使用store
实例的$onAction()
方法来监视actions
的调用
$onAction()
有两个参数:
第一个参数是一个回调函数:
userStore.$onAction(args => {
// 调用的那个action的名字
console.log(args.name);
// store实例
console.log(args.store);
// action成功调用并运行完触发
args.after(() => {
console.log("after");
});
// action抛错或返回一个失败的promise时触发
args.onError(() => {
console.log("出错了");
});
});
第二个参数是固定值: true
当组件卸载
的时候,action
的订阅也会自动卸载,如果我们希望订阅在组件卸载后还能保留,那就传第二个参数
userStore.$onAction(args => {
// 调用的那个action的名字
console.log(args.name);
// store实例
console.log(args.store);
// action成功调用并运行完触发
args.after(() => {
console.log("after");
});
// action抛错或返回一个失败的promise时触发
args.onError(() => {
console.log("出错了");
});
}, true);
Plugins
我们可以通过写插件对pinia
的store
进行一些扩展
和vue一样,通过pinia.use()
注册插件
pinia的插件是一个函数
pinia内部会帮我们调用这个插件函数,并且传回来一个可选的参数context
( 这部分演示的是js的写法,如果是ts还需要加一些东西,放在更后面了
// main.js
import { createApp, markRaw } from 'vue'
import { router } from './router'
import { createPinia } from 'pinia'
const myPiniaPlugin = (context) => {
// defineStore()时传进去的第二个参数
console.log(context.options);
// store实例
console.log(context.store);
// 可以给store实例添加一个全局属性
context.store.globalProperty = '我是一个全局属性'
// 可以给store实例添加一个新的state
context.store.$state.newProperty = context.store.globalProperty;
// $state可读可写
console.log(context.store.$state); // Proxy {newProperty: '我是一个全局属性'}
// 添加第三方库的实例,需要用markRow()包裹,标记为不可响应式
context.store.router = markRow(router)
// 这两个订阅也可以调,在里面处理一些需要的逻辑
context.store.$subscribe(() => {})
context.store.$onAction(() => {})
}
const app = createApp(App)
const store = createPinia()
store.use(myPiniaPlugin)
app.use(store)
在defineStore()
时可以传第三个参数,他会出现在👆的context.options
中,这样我们可以利用自定义的第三个参数来做一些事情
使用ts编写插件:
context
的类型是PiniaPluginContext
,这个要导进来写上
1.如果为store
实例添加了新的属性
需要扩展PiniaCustomProperties
接口
// main.ts
import 'pinia'
import {createPinia, PiniaPluginContext} from 'pinia'
declare module 'pinia' {
export interface PiniaCustomProperties {
globalProperty: string
}
}
const myPiniaPlugin = (context: PiniaPluginContext) => {
// 给store实例添加一个全局属性
context.store.globalProperty = '我是一个全局属性'
}
2.如果给store
实例添加了新的state
需要扩展PiniaCustomStateProperties
接口
...
declare module 'pinia' {
export interface PiniaCustomStateProperties {
newProperty: string
}
}
const myPiniaPlugin = (context: PiniaPluginContext) => {
// 给store实例添加一个全局属性
context.store.$state.newProperty = context.store.globalProperty;
}
...
3.如果添加了新的创建选项,也就是给defineStore()
传了第三个参数
需要扩展DefineStoreOptionsBase
接口
// 这我也不知道举什么例子了
declare module 'pinia' {
export interface DefineStoreOptionsBase {
...
}
}
(最后部分整的有点潦草我清楚我明白,原因是我还没有参透ts🙏)
(等我学成归来xx等我需要用到的时候再研究一下🙏)
(详情请咨询https://pinia.vuejs.org)