1. 安装与注册
1.1 安装 npm i pinia
1.2 注册
/src/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')
2. 创建基本 store 和 基本使用
2.1 新建文件
/src/store/store-name.ts
// 暴露一个枚举类型,用于给不同的 store 命名,防止重复定义
export const enum Names {
Test = 'Test'
}
/src/store/index.ts
import { Names } from "./store-name";
import { defineStore } from "pinia";
// 调用 defineStore 定义一个 store
// 第一个参数是唯一命名空间,第二个参数是一个对象,包含了 state,getters,actions
// 注意名称的定义 useTestStore 使用 use 开头,表示是一个 hook
export const useTestStore = defineStore(Names.Test, {
// state 一个函数,返回一个对象,对象里面存储着各种 state
state: () => {
return {
name: 'lzy',
age: 18
}
},
// 类似 computed,可以修饰一些值
getters: {
},
// 类似 methods,既可以同步,也可以异步提交 state
actions: {
}
})
2.2 基本使用 store
// App.vue
<template>
<!-- 在模板中使用 store 中的 state -->
<h3> Pinia-Test-State:{{ Test.name }} - {{ Test.age }} </h3>
</template>
<script setup lang="ts">
// 引入 store
import { useTestStore } from './store'
// 调用这个 store
const Test = useTestStore()
// console.log(Test) 打印看一下返回的是什么,是一个响应式的对象
</script>
打印结果
3. 修改 state 的五种方式
// App.vue
<template>
<h3> Pinia-Test-State:{{ Test.name }} - {{ Test.age }} </h3>
<button @click="change">修改</button>
</template>
<script setup lang="ts">
import { useTestStore } from './store'
const Test = useTestStore()
const change = () => {
// 1. 直接修改
// Test.name = "LZY"
// 2. 借助 $patch,传递一个对象,对象内属性可以不全写,但不能新增
// Test.$patch({ name: 'LZY'}) // 可以
// Test.$patch({ name: 'LZY', age: 20}) // 也可以
// Test.$patch({ name: 'LZY', gender: '男'}) // 不可以
// 3. 借助 $patch,传递一个函数(参数为 store 中返回的 state),相比第二种的优势在于,可以在函数内添加一些处理逻辑
// Test.$patch((state) => {
// // 可以添加一些处理逻辑
// state.name = 'LZY'
// state.age = 20
// })
// 4. 借助 $state,缺点在于 一定要把属性写全
// Test.$state = {
// name: 'LZY',
// age: 20
// }
// 5. 借助 actions,要在 store 的 actions 定义修改函数,最好一个 state 对应一个 setter
// Test.setName("LZY")
// Test.setAge(20)
}
</script>
对于第 5 种方式,要在 actions 中新增方法
// /src/store/index
import { Names } from "./store-name";
import { defineStore } from "pinia";
export const useTestStore = defineStore(Names.Test, {
state: () => {
return {
name: 'lzy',
age: 18
}
},
// 类似 computed,可以修饰一些值
getters: {
},
// 类似 methods,既可以同步,也可以异步提交 state
actions: {
// 新增的方法 ↓
setName(name: string) {
this.name = name
},
setAge(age: number){
this.age = age
}
}
})
4. 解构 store
4.1 普通解构出来的属性不具有响应式
<template>
<h3> 原始值:{{ Test.name }} - {{ Test.age }} </h3>
<h3> 解构值:{{ name }} - {{ age }} </h3>
<button @click="change">修改</button>
</template>
<script setup lang="ts">
import { useTestStore } from './store'
const Test = useTestStore()
let { name, age } = Test
const change = () => {
Test.age++ // store 本身的 state 是具有响应式的
console.log(age) // 解构出来的变量永远不会跟着变
}
</script>
4.2 借助 storeToRefs 解构出来的属性具有响应式
<template>
<h3> 原始值:{{ Test.name }} - {{ Test.age }} </h3>
<h3> 解构值:{{ name }} - {{ age }} </h3>
<button @click="change">修改</button>
</template>
<script setup lang="ts">
import { useTestStore } from './store'
import { storeToRefs } from 'pinia'
const Test = useTestStore()
// 解构出响应式的属性
let { name, age } = storeToRefs(Test)
const change = () => {
// 无论修改哪个属性,都是响应式的
Test.age++
age.value++
console.log(Test.age, age.value)
}
</script>
5. actions 和 getters
5.1 actions 同步函数
/src/store/index
import { Names } from "./store-name";
import { defineStore } from "pinia";
type User = {
name: string,
age: number
}
const result: User = {
name: 'lzy',
age: 18
}
export const useTestStore = defineStore(Names.Test, {
state: () => {
return {
user: {},
info: ""
}
},
// 类似 computed,可以修饰一些值
getters: {
},
// 类似 methods,既可以同步,也可以异步提交 state
actions: {
// 同步提交
setUser () {
this.user = result
}
}
})
/src/App.vue
<template>
<h3> Actions: {{ Test.user }} </h3>
<button @click="change">提交</button>
</template>
<script setup lang="ts">
import { useTestStore } from './store'
const Test = useTestStore()
const change = () => {
// 调用 actions 的同步方法 setUser(),页面同步更新
Test.setUser()
}
</script>
5.2 actions 异步函数
/src/store/index
import { Names } from "./store-name";
import { defineStore } from "pinia";
type User = {
name: string,
age: number
}
const login = ():Promise<User> => {
return new Promise((resolve) => {
setTimeout(() => resolve({
name: 'lzy',
age: 18
}), 2000)
})
}
export const useTestStore = defineStore(Names.Test, {
state: () => {
return {
user: {},
info: ""
}
},
// 类似 computed,可以修饰一些值
getters: {
},
// 类似 methods,既可以同步,也可以异步提交 state
actions: {
// 异步提交
async setUser () {
const result = await login()
this.user = result
}
}
})
/src/App.vue 中代码不变,页面在异步操作有结果后更新
5.3 actions 可相互调用
/src/store/index
actions: {
// 异步提交
async setUser () {
const result = await login()
this.user = result
this.setInfo()
},
setInfo() {
this.info = "这是我的信息"
}
}
/src/App.vue
<template>
<!-- 可以看到 user 和 info 同步更新,都是异步操作-->
<h3> Actions: {{ Test.user }} </h3>
<h3> Actions 相互调用: {{ Test.info }}</h3>
<hr />
<button @click="change">提交</button>
</template>
<script setup lang="ts">
import { useTestStore } from './store'
const Test = useTestStore()
const change = () => {
Test.setUser()
}
</script>
5.4 getters 计算属性
/src/store/index
getters: {
newInfo():string {
return `更新我的信息-${this.info}`
}
},
/src/App.vue 直接像 state 一样获取即可
<h3> getters: {{ Test.newInfo }}</h3>
5.5 getters 可相互调用
getters: {
newInfo():string {
return this.setNewInfo // 调用下面的这个属性,并返回
},
setNewInfo(): string {
return `更新我的信息-${this.info}`
}
},
6. 实例身上的 API
6.1 $reset
重置 state 成初始状态
<button @click="reset">重置</button>
const reset = () => {
Test.$reset()
}
6.2 $subscribe
监视 state 的变化
Test.$subscribe((args, state) => {
console.log(args)
console.log(state) // 这个就是 state 里面的所有数据
},{
detached: true, // 组件被销毁后,仍继续监听
deep: true, // 深度监听
flush: "pre" // 调用时机,更新之前/中/后
})
6.3 $onAction
监视 action 的调用
Test.$onAction((args) => {
console.log(args) // 打印如下
}, true) // 第二个参数为 detached,true 表示组件卸载后,监视器依然存活
store:就是自定义的那个 store 实例
name:action 的名称
args:传递给 action 的参数
after:接收一个回调,最后时刻调用
onError:接收一个回调,向上传递错误
store.$onAction(({ after, onError }) => {
// 你可以在这里创建所有钩子之间的共享变量,
// 同时设置侦听器并清理它们。
after((resolvedValue) => {
// 可以用来清理副作用
// `resolvedValue` 是 action 返回的值,
// 如果是一个 Promise,它将是已经 resolved 的值
})
onError((error) => {
// 可以用于向上传递错误
})
})
7. pinia 持久化
/src/store/store-storage.ts
import { PiniaPluginContext } from 'pinia'
import { toRaw } from 'vue'
type Options = {
key?: string
}
const setStorage = (key: string, value: any) => {
localStorage.setItem(key, JSON.stringify(value))
}
const getStorage = (key: string) => {
return localStorage.getItem(key) ? JSON.parse(localStorage.getItem(key) as string) : {}
}
// 自定义 piniaPlugin 持久化插件,用户可以传入一个配置项
export const piniaPlugin = (options: Options) => {
// 由于 pinia.use() 需要接收一个函数,并且这个函数只有一个 PiniaPluginContext 类型的参数
// 但是用户又想传入自己的配置项 options
// 这个情况下,就可以用 函数柯里化,用户可以先传入配置项,然后返回一个函数让 pinia.use() 注册
return (context: PiniaPluginContext) => {
const { store } = context
// 根据 options 生成唯一的 key
const key = `${options.key ? options.key : 'defaultKey'}-${store.$id}`
const data = getStorage(key)
// 监听 state 的变化
store.$subscribe(() => {
// 存储到浏览器内存中,记得要用 toRaw 去除响应式
setStorage(key, toRaw(store.$state))
})
return {
...data
}
}
}
/src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'
import { piniaPlugin } from './store/store-storage'
const app = createApp(App)
const pinia = createPinia()
// 自定义 piniaPlugin 插件,然后在 pinia 身上注册
pinia.use(piniaPlugin({
key: 'store'
}))
app.use(pinia)
app.mount('#app')