Pinia 学习报告
CoderBin 3/11/2022 Pinia
# 前言 官方文档 (opens new window)
Pinia.js 是新一代的状态管理器,由 Vue.js团队中成员所开发的,因此也被认为是下一代的 Vuex,即 Vuex5.x,在 Vue3.0 的项目中使用也是备受推崇。
Pinia.js 有如下特点:
- 完整的 typescript 的支持;
- 足够轻量,压缩后的体积只有1.6kb;
- 去除 mutations,只有 state,getters,actions(这是我最喜欢的一个特点);
- actions 支持同步和异步;
- 没有模块嵌套,只有 store 的概念,store 之间可以自由使用,更好的代码分割;
- 无需手动添加 store,store 一旦创建便会自动添加;
# 1. 学习初始
这里使用 vite 来快速创建一个vue3项目,这里选择 TS 编写(使用 JS 或 TS 都可以)
npm init vite@latest
1
# 2. 安装
npm i pinia
1
# 3. 基本配置
# 3.1 在 main.ts 文件中,有以下三个步骤:
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
// 1. 导入 pinia
import { createPinia } from 'pinia'
// 2. 创建 pinia 实例
const pinia = createPinia()
// 3. 挂载到 Vue 根实例
app.use(pinia)
app.mount('#app')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 3.2 创建仓库
在 src 目录下创建 /store/index.ts pinia 仓库
import { defineStore } from "pinia";
export const useMineStore = defineStore('main', {
state: () => {
return {}
},
actions: {
},
getters: {
}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
这样,一个 pinia 仓库就构建完成啦!
# 4. 仓库解读
import { defineStore } from "pinia";
/**
* 1. 定义并导出容器
* 参数 1:容器的 ID,必须唯一,将来 Pinia 会把所有的容器挂载到根容器(命名合法即可)
* 参数 1:选项对象
* 返回值:一个函数,调用得到容器实例
*/
export const useMineStore = defineStore('main', {
//* 类似于组件中的 data,用来存储全局状态
// 1. 必须是函数:这样是为了在服务器渲染的时候避免交叉请求导致的数据转态污染
// 2. 必须是箭头函数:这是为了更好的 TS 类型推导
state: () => {
return {
count: 100,
foo: 'bar'
}
},
//* 类似于组件的 methods,封装业务逻辑,修改 state
// 不要用 箭头函数定义 actions ,否则里面无法使用 this!因为箭头函数绑定的是外部 this
actions: {
},
//* 类似组件的 computed,封装计算属性,具有缓存功能
getters: {
}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 5. 组件中使用仓库数据
这里以 src/components 下的组件为例
<template>
<div>
<p>{{ mainStore.count }}</p>
</div>
</template>
<script lang="ts" setup>
// 1. 按需导入仓库
import { useMineStore } from '../store'
// 2. 创建 store 实例
const mainStore = useMineStore()
// 3. 使用 mianStore 中的 state 里面的数据
console.log(mainStore.count);
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 6. 注意解构赋值的正确方式!
如果直接使用普通的解构赋值方式,被解构出来的数据将不具备响应式!
以下通过点击按钮改变 count 的值来说明这个问题
<template>
<div>
<!-- 页面刷新时会显示初始数据,但是点击按钮修改了store里面count的数据的时候,是不会同步到这里的 -->
<p>{{ count }}</p>
<button @click="handlerChangeState">修改count</button>
</div>
</template>
<script lang="ts" setup>
import { storeToRefs } from 'pinia';
import { useMineStore } from '../store'
const mainStore = useMineStore()
// 普通的解构赋值
const { count } = mainStore
// 点击修改 count 的值,修改 store 里面的值的方式后面会讲,这里不用注意
const handlerChangeState = () => {
mainStore.count++
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
说明:通过普通解构赋值拿出来的数据,是有问题的!这样的数据不再是响应式!是一次性的! 因为 Pinia 其实是把 state 里的数据都做了 reactive 处理了(reactive直接使用普通的解构赋值的数据也不具备响应式)
解决方法:使用 Pinia 官方提供的 API,storeToRefs
<template>
<div>
<!-- 这里的 count 是响应式的!-->
<p>{{ count }}</p>
<button @click="handlerChangeState">修改count</button>
</div>
</template>
<script lang="ts" setup>
// 导入官方提供的 API
import { storeToRefs } from 'pinia';
import { useMineStore } from '../store'
const mainStore = useMineStore()
// 普通的解构赋值
// const { count } = mainStore
// 解决方法:使用 Pinia 官方提供的 API
const {count,foo } = storeToRefs(mainStore)
// 点击修改 count 的值
const handlerChangeState = () => {
mainStore.count++
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
说明:Pinia 把解构出来的数据做 ref 响应式处理了
# 7. 组件内修改 sotre 的数据
依旧使用上面的案例进行说明:
- 方式一:组件中直接修改 state 中的数据
<script lang="ts" setup>
import { useMineStore } from '../store'
const mainStore = useMineStore()
// 点击修改 count 的值
const handlerChangeState = () => {
// 方式一:直接修改 state 中的数据
mainStore.count++
mainStore.foo = 'hello'
}
</script>
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
- 方式二:
$patch({ })接收一个修改对象,如果需要修改多个数据,建议使用 $patch 批量更新,具有性能优化的效果
<script lang="ts" setup>
import { useMineStore } from '../store'
const mainStore = useMineStore()
// 点击修改 count 的值
const handlerChangeState = () => {
// 方式二:使用 $patch({}) 修改数据
mainStore.$patch({
count: mainStore.count+1,
foo: 'hello'
})
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
- 方式三:
$patch((state)=>{ })接收一个函数 更好的批量更新的方式:$patch 一个函数,state 则为store中的 state,也具有性能优化的效果
<script lang="ts" setup>
import { useMineStore } from '../store'
const mainStore = useMineStore()
// 点击修改 count 的值
const handlerChangeState = () => {
// 方式三:使用 $patch((state)=>{ }) 修改数据
mainStore.$patch(state => {
state.count++;
state.foo = 'hello';
state.arr.push(4)
})
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
- 方式四:逻辑比较多的时候可以封装到 actions 做处理
<script lang="ts" setup>
import { useMineStore } from '../store'
const mainStore = useMineStore()
// 点击修改 count 的值
const handlerChangeState = () => {
// 方式四:逻辑比较多的时候可以封装到 actions 做处理
mainStore.changeState(10)
}
</script>
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
回到 store/index.ts 中,在 actions 下编写对应逻辑
import { defineStore } from "pinia";
export const useMineStore = defineStore('main', {
state: () => {
return {
count: 100,
foo: '',
arr: [1, 2, 3]
}
},
actions: {
// 可以通过 this 访问到 state 中的数据
changeState(num: number) {
console.log(this);
console.log(this.count, num);
this.count += num;
this.foo = 'hello';
this.arr.push(4);
}
},
getters: {
}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
总结:建议使用第四种方式,方便后期维护
# 8. getters 的使用
getter 类似计算属性,它并不会修改state的数据,只会通过计算得出结果用作于展示,必须要有返回值 return
# 8.1 函数接收一个可选参数:state 状态对象,好处是会自动判断这个函数的返回值是什么类型
import { defineStore } from "pinia";
export const useMineStore = defineStore('main', {
state: () => {
return {
count: 100,
}
},
actions: {
},
getters: {
count10(state) {
console.log('count 调用了');
return state.count + 10;
// 注意:如果接收了一个 state 参数,内部却用了 this,也是可以的
// return this.count + 10;
},
}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 8.2 注意:如果在 getters 中的函数不接受参数 state,而内部却使用了 this,则必须手动指定返回值类型,否则 TS 类型推导不出来
import { defineStore } from "pinia";
export const useMineStore = defineStore('main', {
state: () => {
return {
count: 100,
}
},
actions: {
},
getters: {
// 手动指定返回值类型
count10(): number {
console.log('count 调用了');
return this.count + 10;
}
}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 8.3 getters 中的函数允许相互调用
- 这里的 newArr 函数过滤了 arr 数组中为3的值并返回一个数组,然后被 newArr1 使用了新的数组,在组件中也可以访问 newArr1 去获取到值。
- 这只是用来演示 getters 中的函数可以相互调用,再以后的项目中,可以实现其他更好的功能。
import { defineStore } from "pinia";
export const useMineStore = defineStore('main', {
state: () => {
return {
arr: [1, 2, 3],
}
},
actions: {
},
getters: {
newArr(state) {
return state.arr.filter(item => item!==3)
},
// getters 里面的函数可以互相调用到,直接使用 this 即可
newArr2() {
return this.newArr
}
}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22