Pinia的使用(在Vue3和TypeScript的环境下)
1. 创建项目
1.1 脚手架搭建
npm init vite@latest
后面操作见图示
1.2 安装pinia
该笔记, pinia基于版本2.0.11
npm i pinia@2.0.11
或者 yarn add pinia@2.0.11
2. Pinia - Store
2.1 创建Store
在入口文件main.ts
中:
import { createApp } from "vue";
import App from "./App.vue";
// 引入pinia
import { createPinia } from "pinia";
// 创建 Pinia 实例
const pinia = createPinia();
const app = createApp(App);
// 挂载到 Vue 根实例
app.use(pinia);
app.mount("#app");
2.2 Store详讲
在src下, 新建文件夹store
, 再新建index.ts
在index.ts中:
/**
* 一般在容器中做这4件事
* 1. 定义容器并导出
* 2. 使用容器中的state
* 3. 修改容器中的state
* 4. 使用容器中的action
*/
import { defineStore } from "pinia";
/**
* 1. 定义容器并导出
* 参数一: 容器ID, 唯一, 将来 Pinia 会把所有的容器挂载到根容器
* 参数二: 选项对象
* 返回值: 函数, 调用的时候要空参调用, 返回容器实例
*/
export const mainStore = defineStore('main', {
/**
* 类似组件的 data, 用于存储全局的的状态
* 注意:
* 1.必须是函数, 为了在服务端渲染的时候避免交叉请求导致的数据交叉污染
* 2.必须是箭头函数, 为了更好的 TS 类型推导
*/
state: () => {
return {
count: 100,
foo: 'bar',
age: 18
}
},
/**
* 类似组件的 computed, 用来封装计算属性, 具有缓存特性
*/
getters: {
},
/**
* 类似组件的 methods, 封装业务逻辑, 修改state
* 注意: 里面的函数不能定义成箭头函数(函数体中会用到this)
*/
actions: {
}
})
2.3 使用store中的状态
2.3.1 简单访问store状态
把components/HelloWorld.vue
中的代码清空, 加入以下代码
<template>
<div v-text="mainStoreI.count"></div>
</template>
<script lang="ts" setup>
import {mainStore} from '../store'
const mainStoreI = mainStore()
console.log(mainStoreI.count);
</script>
2.3.2 访问及简单修改store(标准操作)
但是上面的访问未免过于麻烦, 使用 count、foo 前面还要加 mainStoreI , 如果直接采用解构的方式, 会导致数据不是响应式的, 具体解决方式如下:
import { storeToRefs } from ‘pinia’;
<template>
<div v-text="mainStoreI.count"></div>
<hr>
<p>count: {{ count }}</p>
<p>foo: {{ foo }}</p>
<p>age: {{ age }}</p>
<button @click="handleClick">修改count数据</button>
</template>
<script lang="ts" setup>
import {mainStore} from '../store'
import { storeToRefs } from 'pinia';
const mainStoreI = mainStore()
const {count, foo, age} = storeToRefs(mainStoreI)
/** 修改store中的数据 */
const handleClick = () => {
// 修改单个数据,方式一
// count.value++
// 修改单个数据,方式二
mainStoreI.count++
}
</script>
2.4 store - $patch 批量简单修改
多个数据修改,建议使用
$patch
批量更新,不单纯是写法优化,还有性能的优化
/** 修改store中的数据 */
const handleClick = () => {
// 哪些数据项需要修改就写数据项
mainStoreI.$patch({
count: ++mainStoreI.count,
foo: 'hello',
})
}
2.5 $patch怎么更新数组?
方式一: 利用ES6展开运算符
const handleClick = () => {
mainStoreI.$patch({
// 更新数组
arr: [...mainStoreI.arr, 4]
})
}
方式二: $patch可以接收一个函数(推荐)
const handleClick = () => {
// 给$patch传入一个函数,函数体中修改数据项
// 形参state即为容器
mainStoreI.$patch((state) = {
state.count++;
state.foo = 'hello';
state.arr.push(4)
})
}
2.6 总结: 修改状态数据的四种方式
3. Pinia - actions
- 修改 Store 状态时, 如果逻辑较多, 可以借助 actions
- actions 中也可以使用 $patch, 见下面代码
注意:
- actions 中的函数, 不能定义成箭头函数
- 因为箭头函数中没有this, 在运行时, 会向外部的作用域找
src\store\index.ts
/**
* 类似组件的 methods, 封装业务逻辑, 修改state
*/
actions: {
changeState(num: number): void {
// this访问当前容器的实例
this.count += num
this.foo='你好啊'
this.arr.push(555)
// 这里也可以使用$patch
// this.$patch({})
// this.$patch(state => {})
}
}
业务代码
/** 修改store中的数据 */
const handleClick = () => {
// 当逻辑较多时,可以封装到actions中处理
mainStoreI.changeState(10)
}
4. Pinia - getters
-
具有缓存特性
-
getters的定义有两种
-
接受一个形参, 代表容器状态实例(数据)
count10 (state) { state.count + 10}
-
不使用形参, 函数体中使用
this
, 必须手动指定返回值类型原因: 无法推导this的类型, 会导致编译报错, 需手动指定返回值类型
count10 (): number { this.count + 10 }
-
(不推荐) 既使用state, 也使用this, 这个时候可以不手动指定返回值类型
这种写法怪怪的, 写了形参不用
count10 (state){ this.count + 10 }
-
定义:
export const mainStore = defineStore('main', {
state: () => {
return {
count: 100,
foo: 'bar',
age: 18,
arr:[99, 55]
}
},
/**
* 类似组件的 computed, 用来封装计算属性, 具有缓存特性
*/
getters: {
// 每次使用的 count10 都是在 count 的基础上加10
count10 (state) {
console.log('count10 调用了');
return state.count+10
}
}
})
如图, count10 访问了3次, 但是由于缓存, 实际 getters 只运行了一次:
5. Store相互调用
如果存在相互调用的情况, 和在业务组件中使用一致
- 在A中导入B
- 实例化
- 调用B中的方法等…