第三章:状态管理
介绍
框架使用的状态管理工具是 pinia
,它允许我们跨页面/跨组件进行状态共享,在 Pinia
中,核心概念是 store
。每个 store
就像一个独立的状态容器。框架中存放位置于 src/store
目录中,在 src/store/index.js
文件中定义了框架中所有使用 pinia
的对象。
特别注意
当页面使用F5刷新的时候,pinia
中保存的状态是不会被保留的,页面刷新会重新加载整个 javascript
环境,因此 Pinia
store
会重置为其初始状态
定义和使用
定义
使用函数 defineStore
来创建一个 store
, 第一个参数 counter
是 store
的唯一ID,第二个参数是一个选项对象,包含:
- state: 定义store的初始状态
- getters: 类似于计算属性,用于派生状态
- actions: 包含可以改变状态的方法
javascript
import { defineStore } from 'pinia'
// 定义
const useCounterStore = defineStore('counter', {
state: () => {
count: 0,
},
getters: {
doubleCount: (state) => state.count * 2
},
actions: {
increment (name) {
this.count++
}
}
})
使用
使用 useCounterStore
函数来获取 store
实例
- 可以直接访问 state (counter.count)
- 可以直接调用 actions (counter.increment())
- Getters 的使用方式和 state 相同
vue
<script setup>
import { useCounterStore }from "@/stores/counter"
// 获取store实例
const counter = useCounterStore()
// 方法
function handleClick(){
counter.increment()
}
</script>
<template>
<div>
<p>count:{{ counter.count }}</p>
<p>Double count:{{ counter.doubleCount }}</p>
<button @click="handleClick">Increment</button>
</div>
</template>
框架中使用实例
在 saiadmin
框架中,我们有一项设计是系统的字典数据使用,因为数据字典是允许跨页面、跨组件访问的,所以很适合采取 pinia
状态进行保存,我们初步设计在用户登录成功获取基本数据和配置后,加载系统的字典数据,下面来介绍详细的使用步骤。
- 定义字典数据的
store
, 首先创建文件src/store/modules/dict.js
, 创建内容如下:
javascript
import { defineStore } from 'pinia'
import commonApi from '@/api/common'
// 定义字典store,名称是dict
const useDictStore = defineStore('dict', {
// 字典数据是数组,我们定义一个data来进行保存
state: () => ({ data: undefined }),
getters: {
// 获取store状态
getState() {
return { ...this.$state }
}
},
actions: {
//给字典数据赋值
setInfo(data) {
this.$patch(data)
},
// 初始化字典数据
async initData() {
const { data } = await commonApi.dictAll()
this.data = data
}
}
})
export default useDictStore
- 将字典
store
进行引用,引入后我们就可以在其他页面进行调用了,引用store
的文件是src/store/index.js
javascript
import { createPinia } from 'pinia'
import useUserStore from './modules/user'
import useAppStore from './modules/app'
import useTagStore from './modules/tag'
import useKeepAliveStore from './modules/keepAlive'
import useIframeStore from './modules/iframe'
import useConfigStore from './modules/config'
import useMessageStore from './modules/message'
// 引入字典 store
import useDictStore from './modules/dict'
const pinia = createPinia()
export {
useUserStore,
useAppStore,
useTagStore,
useKeepAliveStore,
useIframeStore,
useConfigStore,
useMessageStore,
useDictStore // 对外暴露字典 store
}
export default pinia
- 在用户数据加载成功后,写入字典数据到
store
中,在文件src/store/modules/user.js
中如下
javascript
requestUserInfo() {
return new Promise((resolve, reject) => {
loginApi.getInfo().then(async (response) => {
if (!response || !response.data) {
this.clearToken()
await router.push({ name: 'login' })
reject(false)
} else {
this.setInfo(response.data)
// 初始化字典数据
const dictStore = useDictStore()
await dictStore.initData()
homePage.children = webRouter[0].children
this.setMenu(this.routers)
this.routers = removeButtonMenu(this.routers)
this.routers.unshift(homePage)
await this.setApp()
resolve(response.data)
}
})
})
},
- 页面或者组件中进行调用,例如我们封装的组件
sa-radio
中,我们通过传递字典名称,就能渲染对应字典的数据,是因为在这个页面中, 我们通过store
获取了字典列表,然后在字典列表中拿到了字典数据
vue
<template>
<a-radio-group v-model="value" :direction="props.direction" :type="props.type" :disabled="props.disabled" @change="handleChangeEvent($event)">
<template v-for="(item, index) in dictList[props.dict] ?? []">
<a-radio :value="item.value">{{ item.label }}</a-radio>
</template>
</a-radio-group>
</template>
<script setup>
import { ref, watch } from 'vue'
// 引用字典store
import { useDictStore } from '@/store'
// 定义字典示例
const dictList = useDictStore().data
const emit = defineEmits(['update:modelValue', 'change'])
const value = ref()
const props = defineProps({
modelValue: { type: [String, Number] },
type: { type: String, default: 'radio' },
dict: { type: String, default: '' },
disabled: { type: Boolean, default: false },
direction: { type: String, default: 'horizontal' },
})
</script>