前端数据管理
解决这个问题的最常见的一种思路就是:专门定义一个全局变量,任何组件需要数据的时候都去这个全局变量中获取。一些通用的数据,比如用户登录信息,以及一个跨层级的组件通信都可以通过这个全局变量很好地实现。在下面的代码中我们使用 _store 这个全局变量存储数据。
ini
// 比如挂一个全局的变量
window._store = {}
其他组件和这个量的交互

但这样就会产生一个问题,window._store 并不是响应式的,如果在 Vue 项目中直接使用,那么就无法自动更新页面。所以我们需要用 ref 和 reactive 去把数据包裹成响应式数据,并且提供统一的操作方法,这其实就是数据管理框架 Vuex 的雏形了。
Vuex是什么
Vuex 就相当于我们项目中的大管家,集中式存储管理应用的所有组件的状态。
java
// 安装
npm install vuex@next
php
// store/index.js
import { createStore } from 'vuex'
const store = createStore({
state () {
return {
count: 666
}
},
mutations: {
add (state) {
state.count++
}
}
})
在项目入口文件 src/main.js 中,使用 app.use(store) 进行注册,这样 Vue 和 Vuex就连接上了。
javascript
import { createApp } from 'vue'
import App from './App.vue'
import router from './router/index'
const app = createApp(App)
app.use(store)
.use(router)
.mount('#app')
新增一个 components/Count.vue
xml
<template>
<div @click="add">
{{count}}
</div>
</template>
<script setup>
import { computed } from 'vue'
import {useStore} from 'vuex'
let store = useStore()
let count = computed(()=>store.state.count)
function add(){
store.commit('add')
}
</script>
实现一个 简单的点击累加器
此处有点区别在于 count 不是使用 ref 直接定义,而是使用计算属性返回了 store.state.count,也就是刚才在 src/store/index.js 中定义的 count。add 函数是用来修改数据,这里我们不能直接去操作 store.state.count +=1,因为这个数据属于 Vuex 统一管理,所以我们要使用 store.commit('add') 去触发 Vuex 中的 mutation 去修改数据。
数据什么时候用 Vuex 来管理 什么时候用 ref 这样区分
对于一个数据,如果只是组件内部使用就是用 ref 管理;如果我们需要跨组件,跨页面共享的时候,我们就需要把数据从 Vue 的组件内部抽离出来,放在 Vuex 中去管理。
比如项目中的登录用户名,页面的右上角需要显示,有些信息弹窗也需要显示。这样的数据就需要放在 Vuex 中统一管理,每当需要抽离这样的数据的时候,我们都需要思考这个数据的初始化和更新逻辑。

手写mini Vuex
javascript
// src/store/gvuex.js
import { inject, reactive } from 'vue'
const STORE_KEY = '__store__'
function useStore() {
return inject(STORE_KEY)
}
function createStore(options) {
return new Store(options)
}
class Store {
constructor(options) {
this._state = reactive({
data: options.state()
})
this._mutations = options.mutations
}
}
export { createStore, useStore }
上面的代码还暴露了 createStore 去创建 Store 的实例,并且可以在任意组件的 setup 函数内,使用 useStore 去获取 store 的实例
javascript
class Store {
// main.js入口处app.use(store)的时候,会执行这个函数
install(app) {
app.provide(STORE_KEY, this)
}
}
Store 类内部变量 _state 存储响应式数据,读取 state 的时候直接获取响应式数据 _state.data,并且提供了 commit 函数去执行用户配置好的 mutations。
javascript
import { inject, reactive } from 'vue'
const STORE_KEY = '__store__'
function useStore() {
return inject(STORE_KEY)
}
function createStore(options) {
return new Store(options)
}
class Store {
constructor(options) {
this.$options = options
this._state = reactive({
data: options.state
})
this._mutations = options.mutations
}
get state() {
return this._state.data
}
commit = (type, payload) => {
const entry = this._mutations[type]
entry && entry(this.state, payload)
}
install(app) {
app.provide(STORE_KEY, this)
}
}
export { createStore, useStore }
使用 这个 gvuex
javascript
import {useStore} from '../store/gvuex'
let store =useStore()
let count = computed(()=>store.state.count)
function add(){
store.commit('add')
}
Vuex 使用
Vuex 就是一个公用版本的 ref,提供响应式数据给整个项目使用
使用 getters 类似于 Vue 中的 computed
javascript
import { createStore } from 'vuex'
const store = createStore({
state () {
return {
count: 666
}
},
getters:{
double(state){
return state.count*2
}
},
mutations: {
add (state) {
state.count++
}
}
})
export default store
组件中使用 时
csharp
let double = computed(()=>store.getters.double)
异步数据获取
在 Vuex 中,mutation 的设计就是用来实现同步地修改数据。如果数据是异步修改的,我们需要一个新的配置action。现在我们模拟一个异步的场景,就是点击按钮之后的 1 秒,再去做数据的修改。
javascript
import { createStore } from 'vuex'
const store = createStore({
state () {
return {
count: 666
}
},
// ...
actions:{
asyncAdd({commit}){
setTimeout(()=>{
commit('add')
},1000)
}
}
})
export default store
action 并不是直接修改数据,而是通过 mutations 去修改,actions 的调用方式是使用 store.dispatch
csharp
function asyncAdd(){
store.dispatch('asyncAdd')
}
Vuex 在整体上的逻辑如下图所示,从宏观来说,Vue 的组件负责渲染页面,组件中用到跨页面的数据,就是用 state 来存储,但是 Vue 不能直接修改 state,而是要通过actions/mutations 去做数据的修改。


在决定一个数据是否用Vuex 来管理的时候,核心就是要思考清楚,这个数据是否有共享给其他页面或者是其他组件的需要。如果需要,就放置在 Vuex 中管理;如果不需要,就应该放在组件内部使用ref 或者 reactive 去管理。
Pinia
Vuex 由于在 API 的设计上,对 TypeScript 的类型推导的支持比较复杂,用起来很是痛苦。Pinia 的 API 的设计非常接近 Vuex5 的提案,首先,Pinia 不需要Vuex 自定义复杂的类型去支持 TypeScript,天生对类型推断就非常友好,并且对 VueDevtool 的支持也非常好