如何使用 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 的支持也非常好

相关推荐
weixin_462446234 分钟前
nodejs 下使用 Prettier 美化单个 JS 文件(完整教程)
开发语言·javascript·ecmascript
岭子笑笑24 分钟前
vant 4 之loading组件源码阅读
前端
q_191328469527 分钟前
基于SpringBoot2+Vue2的行业知识答题考试系统
java·vue.js·spring boot·mysql·毕业设计·计算机毕业设计·演示文稿
hxmmm27 分钟前
自定义封装 vue多页项目新增项目脚手架
前端·javascript·node.js
ETA827 分钟前
JS执行机制揭秘:你以为的“顺序执行”,其实是V8引擎在背后搞事情!
前端·javascript
鹏北海-RemHusband29 分钟前
微前端实现方式:HTML Entry 与 JS Entry 的区别
前端·javascript·html
行走的陀螺仪1 小时前
JavaScript 装饰器完全指南(原理/分类/场景/实战/兼容)
开发语言·javascript·ecmascript·装饰器
瘦的可以下饭了1 小时前
3 链表 二叉树
前端·javascript
我那工具都齐_明早我过来上班1 小时前
WebODM生成3DTiles模型在Cesium地图上会垂直显示问题解决(y-up-to-z-up)
前端·gis
粉末的沉淀1 小时前
jeecgboot:electron桌面应用打包
前端·javascript·electron