引入 Pinia

1
2
3
4
5
6
7
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'

const app = createApp(App)
app.use(createPinia())
app.mount('#app')

创建 Store

创建 src/stores/index.ts

1
2
3
4
5
6
7
8
9
10
11
import { defineStore } from 'pinia'
import { Todo } from './todo'

export const useTodoStore = defineStore('todo', {
state: () => ({
todo: [] as Todo[],
}),
getters: {},
actions: {},
hydrate: (storeState, initialState) => {},
})
  • Pinia 抛弃了 Vuex 的嵌套 Store 和 module,直接支持多 Store,因此文件夹命名为 stores 而不是 store。同时与 Vuex 使用不同的命名有利于 Vuex 的项目向 Pinia 平滑迁移。
  • defineStore 第一个参数是 Store 的 id,必须唯一。
  • defineStore 返回的是一个函数,通过调用达到类似 Vuex 中 useStore 的效果。
  • Store 函数的命名一般为 useIdStore
  • getters 是 Store 的计算属性。
  • Pinia 的 actions 支持同步和异步。
  • hydrate 用于 SSR。

使用 State

1
2
3
import { useTodoStore } from './stores'

const todo = useTodoStore()
1
2
3
<div v-for="item in todo.todo" :key="item.id">
<Todo :title="item.name" :desc="item.description" v-model="item.done"></Todo>
</div>

useIdStore 函数的返回值上可以访问到该 Store。

如果你想在 Options API 中使用 Store,可以使用 setup 钩子:

1
2
3
4
5
6
7
8
import { useTodoStore } from './stores'

export default {
setup() {
const todo = useTodoStore()
return { todo }
},
}

如果你不了解 Composition API 或 setup 钩子,记住 setup 钩子的返回内容将被暴露在组件实例上,因此你可以在 Options API 中这样使用:this.todo

解构 Store

解构赋值使得在使用变量的时候更方便,但 Store 不能随便解构,否则会失去响应性而变成一个单纯的值变量。

如果需要解构 store,需要使用 storeToRefs

1
2
3
4
import { useTodoStore } from './stores'
import { storeToRefs } from 'pinia'

const { todo } = storeToRefs(useTodoStore())

由于该 API 会使用 ref 包装解构出来的值,因此使用的时候需要遵循 ref 的方法:使用 todo.value 来访问到包装里的值。因此,对 Store 解构并不总是会简化代码。

修改 State

直接修改

与 Vuex 不同,你可以直接在 Store 实例上对 State 进行修改而不需要创建一个 mutation,Pinia 将会自动完成 mutation 的创建。

1
store.count++

$patch

如果你需要同时修改多个 State,那么 $patch 是一个很好的选择。

1
2
3
todo.$patch({
todo: [],
})

$patch 中传入的对象将被合并到 State 对象中。

另外,如果要对 State 中的数组进行 push 等操作,$patch 还接受一个 patch 函数,可以在 patch 函数中对 State 进行修改。

1
2
3
4
5
6
7
8
todo.$patch(state => {
state.todo.push({
id: parseInt((Math.random() * 100000).toFixed(0)),
name: 'Task 1',
description: 'Eat sleeping dozen doug.',
done: false,
})
})

替换 State

可以更改 Store 实例上的 $state 属性实现对整个 State 进行替换:

1
todo.$state = { todo: [] }

重置 State

可以通过 Store 实例上的 $reset 方法将 State 重置为初始值:

1
todo.$reset()

订阅 State

通过 Store 实例上的 $subscribe 方法订阅 State 的变化,相当于 State 的 watch

1
store.$subscribe((mutation, state) => {})

mutation 有如下属性:

  • type:mutation 类型,值为 directpatch objectpatch function
  • storeId:Store 的 id
  • payload:仅在 patch object 时有效 此对象是传递给 $patch 的对象

创建 Getter

Getter 相当于 State 的计算属性,其使用方式与 Options API 中定义计算属性类似:

1
2
3
doneTodos(state) {
return state.todo.filter(item => item.done)
}

Pinia 支持在 Getter 中使用 this 访访问 Store 实例,但此时会导致 TypeScript 不能推导返回值类型,需要显式定义返回值类型:

1
2
3
doneTodos(): Todo[] {
return this.todo.filter(item => item.done)
}

使用 Getter

你可以像直接使用 State 一样访问 Getter:

1
2
3
<div v-for="item in todo.doneTodos" :key="item.id">
<Todo :title="item.name" :desc="item.description" v-model="item.done"></Todo>
</div>

访问其他 Getter

由于在 Getter 中可以使用 this 访问 Store 实例,因此你也可以在 this 中直接访问其他 Getter。

创建 Action

虽然 Pinia 支持直接修改 State,但如果有一些复杂的、只与 State 有关的业务逻辑,还是建议封装到 Action 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
export const useStore = defineStore('main', {
state: () => ({
counter: 0,
}),
actions: {
increment() {
this.counter++
},
randomizeCounter() {
this.counter = Math.round(100 * Math.random())
},
},
})

要使用 Action,直接在 Store 实例上调用即可。

1
countStore.randomizeCounter()

异步 Action

Pinia 中不像 Vuex 一样同步和异步的方法分别在 mutations 和 actions 中,Pinia 的 Action 同时支持同步与异步。下面是异步 Action 的示例:

1
2
3
4
5
6
7
8
9
10
11
actions: {
async registerUser(login, password) {
try {
this.userData = await api.post({ login, password })
showTooltip(`Welcome back ${this.userData.name}!`)
} catch (error) {
showTooltip(error)
return error
}
}
}

参考

  1. Pinia - The Vue Store that you will enjoy using