状态管理
我们知道组件与组件之间可以传递信息,那么我们就可以将一个信息作为组件的独立状态(例如,单个组件的颜色)或者共有状态(例如,多个组件是否显示)在组件之传递,这样的话我们希望这个信息在所有组件中共享,这样就可以监控所有组件的状态,但是一般的信息传递方式想要在所有组件传递一个信息会形成一个复杂的关系网,不利于管理且中间组件若产生异常,这个关系网就会断裂,整个网页的组件就会变的无法监控,
所以我们需要一个工具来管理所有组件的状态,他需要以下几点功能
1.能够注册一个全局状态(store),形成一个中间件,让所有组件都能访问到,
2.当这个全局状态被某一个组件影响改变时,它能够让其他组件更新这个状态,
3.将信息独立出来,不因为一个组件错误,产生全局错误
vuex和pinia
vuex和pinia都是vue的状态管理工具,它们的作用相同,用法几乎没有区别,但是pinia的结构更加简洁,同时对Ts的兼容性更强。
vuex和pinia的核心
vuex | pinia |
state --- 状态信息 | state --- 状态信息 |
getter --- 选择性的读取信息 | getter --- 选择性的读取信息 |
mutation --- 可以执行同步操作 | action --- 可以执行同步和异步操作 |
action --- 可以执行异步操作 返回一个mutation |
vuex和pinia的关系
Pinia 起源于一次探索 Vuex 下一个迭代的实验,因此结合了 Vuex 5 核心团队讨论中的许多想法。最后,我们意识到 Pinia 已经实现了我们在 Vuex 5 中想要的大部分功能,所以决定将其作为新的推荐方案来代替 Vuex。
这两个工具相似度很高,作用也一致,当你熟练掌握其中一个时,另一个也能够轻松使用,这里我们还是使用pinia作为项目的状态管理器
pinia的基本使用
手动引入pinia
新建一个vue项目
npm create vue@latest
输入完项目名称后一路回车即可,对于是否引入pinia的选项我们暂时不选则,
这里我们在src的目录下新建一个文件夹store,并在store下新建一个index.js,然后删除components文件夹的原内容,并新建两个vue文件
安装pinia包
npm install pinia
安装完成后可以在package中看到,然后我们开始导入pinia
main.js:
import './assets/main.css'
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'
createApp(App).use(createPinia()).mount('#app')
导入完成后我们在index.js中配置pinia
index.js:
import {defineStore} from 'pinia'
export const useStore = defineStore('counter', {
state: () => {
return {
// 定义状态
counter: 0
}
},
// 定义计算属性
getters: {
// 定义计算属性
getCounter: (state) => {
return state.counter>=5?state.counter:"数据不足"
}
},
// 定义操作
actions: {
// 定义操作
addCounter() {
this.counter++
}
}
// 定义其他选项
// ...
})
注意 :第一个参数counter是返回store的一个标识,在一个项目中,我们可以新建多个store来表示多个状态,每个状态都有一个标识;第二个参数是一个对象,里面包含了state定义状态可以返回多个值(任意类型)表示状态,getter定义了一个属性运算的方法,可以用来过滤数据,只拿需要的数据(外部使用这个方法时不带括号),action定义了一个操作(可以 是异步操作,外部调用是要带括号);
然后我们再对App.vue,myComA.vue,myComB.vue引入store中间件
App.vue,
<script setup>
import myComA from './components/myComA.vue'
import myComB from './components/myComB.vue'
import { useStore } from './store/index.js'
const store = useStore()
</script>
<template>
<p>{{ store.getCounter }}</p>
<button @click="store.addCounter()">counter = {{ store.counter }}</button>
<myComA/>
<myComB/>
</template>
myComA.vue,
<script setup>//组合式API
import { useStore } from '../store/index'
const store = useStore()
</script>
<template>
<div>myComA</div>
<p>{{ store.counter }}</p>
</template>
myComB.vue
<script setup>//组合式API
import { useStore } from '../store/index'
const store = useStore()
</script>
<template>
<div>myComB</div>
<p>{{ store.counter }}</p>
</template>
这里要注意import引入index文件的路径有所不同
完整这些后我们启动项目
npm run dev
点击一次按钮,store的counter值就加一,当这个值大于等于5时,会在App.vue中显示出来,否则显示数据不足,App.vue,myComA.vue,myComB.vue 3个组件中均有显示,说明3个组件都访问到了数据,它们绑定了一个共同的信息,即便我们移除了myComB.vue其他组件也没有受到影响
这样我们就为每个组件都设置了一个状态,
系统引入pinia
掌握了手动引入pinia后,我们可以再新建一个项目查看以下系统的pinia
npm create vue@latest
我们可以看到它是有一个stores文件夹的,同时再main.js,counter.js,package.json中也是有对应的配置的
我们可以观察到,它的配置方式和我们手动配置的方式有所不同,
其实这个pinia的配置类似于setup函数的结构,有选择式风格和组合式风格,这里采用的是组合式风格,2者的效果是完全一致的,可以自行对比两种方法
我们可以引入之前项目中App.vue,myComA.vue,myComB.vue 3个组件(注意这里的方法和文件名称有所改变)再启动项目查看
app.vue:
<script setup>
import myComA from './components/myComA.vue'
import myComB from './components/myComB.vue'
import { useCounterStore } from './stores/counter.js'
const store =useCounterStore()
</script>
<template>
<p>{{ store.doubleCount }}</p>
<button @click="store.increment()">counter = {{ store.count }}</button>
<myComA/>
<myComB/>
</template>
myComA.vue:
<script setup>//组合式API
import { useCounterStore } from '../stores/counter.js'
const store = useCounterStore()
</script>
<template>
<div>myComA</div>
<p>{{ store.count }}</p>
</template>
myComB.vue:
<script setup>//组合式API
import { useCounterStore } from '../stores/counter.js'
const store = useCounterStore()
</script>
<template>
<div>myComB</div>
<p>{{ store.count }}</p>
</template>
npm run dev
可以看到实现了状态共享,在实际应用中我们使用系统引入pinia后,可以自己手动的将pinia的相关文件调整成我们需要的结构
扩展:pinia数据持久化
登录状态的数据问题
pinia的状态共享功能最常见的使用就是登录状态的共享,它可以保存当前页面的一个登录状态,这样当你在登录页面登录成功后,其他页面可以共享这个登录状态,在一个页面退出登录后,其他页面也能够共享,这样网页就能区分用户和游客了。
但是,pinia是基于内存实现的,当你刷新页面后,pinia保存的可共享数据就会刷新,这样我们就需要重新登录,我们希望用户登录后登录状态应该由网络请求返回的token(一段有时间限制会过期的数据)保存,所以我们需要实现pinia的数据持久化,让页面刷新后仍可以保留登录状态,
实现数据持久化
想在浏览器内存下一串数据不会因为刷新而清空,我们可以想到localStorage就有这个功能,
pinia提供了一个插件,基于localStorage实现了数据的缓存,这样页面刷新后,pinia会先从localStorage中读取数据(一般是token)验证用户的登录状态,这样就不用反复登录了
pinia-plugin-persistedstate
安装插件:快速开始 | pinia-plugin-persistedstate (prazdevs.github.io)
npm i pinia-plugin-persistedstate
安装完成后可以在package.json中查看
注册插件:
在main.js中注册引入
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import {piniaPluginPersistedstate} from 'pinia-plugin-persistedstate'
const app = createApp(App)
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
app.use(pinia)
app.mount('#app')
在store下创建pinia对象的文件内启用插件
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
},
{
persist: true,//开启路由缓存
},
)
App.vue
<script setup>
import {useCounterStore} from '@/stores/counter'
const {count,increment} = useCounterStore();
increment();//action方法,使用pinia的数据
console.log(count);
</script>
<template></template>
<style scoped></style>
这个counter存储的数据就会被保留在本地了
这样每次打开执行这个自增的 方法时会先从原来保存的数据执行,而不会因为刷新内存丢失掉数据,从0开始执行