如何使用 Vuex 设计你的数据流

前端数据管理

解决这个问题的最常见的一种思路就是:专门定义一个全局变量,任何组件需要数据的时候都去这个全局变量中获取。一些通用的数据,比如用户登录信息,以及一个跨层级的组件通信都可以通过这个全局变量很好地实现。在下面的代码中我们使用 _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 的支持也非常好

相关推荐
李雨泽8 小时前
通过 Prisma 将结构推送到数据库
前端
前端小万8 小时前
使用 AI 开发一款聊天工具
前端·全栈
咖啡の猫9 小时前
Vue消息订阅与发布
前端·javascript·vue.js
下一站丶9 小时前
【JavaScript性能优化实战】
开发语言·javascript·性能优化
GIS好难学9 小时前
Three.js 粒子特效实战③:粒子重组效果
开发语言·前端·javascript
申阳9 小时前
Day 2:我用了2小时,上线了一个还算凑合的博客站点
前端·后端·程序员
刺客_Andy9 小时前
React 第四十七节 Router 中useLinkClickHandler使用详解及开发注意事项案例
前端·javascript·react.js
爱分享的鱼鱼9 小时前
Java实践之路(一):记账程序
前端·后端
爱编码的傅同学10 小时前
【HTML教学】成为前端大师的入门教学
前端·html