Vue3全家桶之数据管理

news2025/1/8 11:31:09

我们都知道Vue框架是通过数据驱动的,所以数据的重要性不言而喻,那么有哪些数据需要管理又是如何进行管理的呢?本节我们就来聊一聊Vue3中的数据管理。

哪些数据需要管理?

在我们的前端项目中,都会有哪些数据呢?

从组件的角度来看,我们可以将数据划分为组件内部的数据和组件之间共用的数据。

组件内部的数据很好理解,我们在介绍组件化开发时已经定义了大量这样的数据,比如控制组件的显示隐藏、弹框的标题、内容等等,这些数据一般都只在组件内部使用,与我们的业务逻辑关系不大,只是用来控制组件的各个状态。

组件之间的共用数据一般指的是与业务逻辑相关的数据,需要在各个组件之间流通共享,比如用户登录时的用户名信息,可能在多个组件中都需要显示用户名,某个地方修改用户名后,其他组件中也要同步的更新显示。

组件内的数据就交给各个组件自己去管理就好了,反正也不会对外界产生影响,所以我们主要关注的还是组件之间共用的数据要如何管理。

最容易想到的方法就是全局变量了,既然多个组件都要使用,在顶层挂载到全局变量中不就好了?

window.usename = 'xxx'

这样做确实可以在所有组件中都能访问到usename变量,但是修改用户名后却无法做到数据同步,再想一下,要想做到数据同步我们需要的不就是一个响应式的数据嘛,联想到响应式章节中的内容,我们将一个响应式的数据绑定到全局不就解决了嘛,这就是状态管理工具Vuex的基本实现原理。

Vuex在Vue3中的使用

那么Vuex究竟是用来干什么的呢?

当我们项目中的业务数据越来越多时,不同的开发人员可能都会有自己定义的数据和更新数据的方法,同一个数据可能A定义了,B也定义了,不但资源浪费了,数据的同步也变的很困难。还有种可能,同一个数据,A用方法1去更新,B用方法2去更新,一旦数据出现了问题,数据的处理流程就非常难以跟踪。

所以我们需要对数据进行集中定义管理,统一对外暴露更新方法——这就是Vuex做的事情。

下面我们就来看一下在Vue3中是如何使用Vuex的。

  1. 安装Vuex。
npm install vuex@next --save
  1. 在store文件夹下新建index.js,创建Store实例。
import { createStore } from 'vuex';

const store = createStore({
    state() {
        return {
            count: 0
        }
    }
})

export default store;

我们来定义一个共享的数据count,并新增一个修改数据的方法。

import { createStore } from 'vuex';

const store = createStore({
    state() {
        return {
            count: 0
        }
    },
    mutations: {
        add (state) {
            state.count++
        }
    },
})

export default store;

从上面的代码中可以看到,我们在mutations属性中新增了add方法,每次执行会让count的值加一。

  1. 在main.js中注入store。
  import { createApp } from 'vue'

  import App from './App.vue'
  import router from './router/router';
+ import store from './store';

  ...
  const app = createApp(App);
  
+ app.use(router).use(store).mount('#app');
  1. 在组件中通过useStore引入store实例,访问count数据并触发count的更新事件。
<template>
    <div>{{count}}</div>
    <button @click="add">1</button>
</template>

<script setup>
    import {useStore} from 'vuex';

    const store = useStore();
    let count = computed(() => store.state.count) 
    
    function add () {
        // 调用mutations中的add方法
        store.commit('add');
    }
</script>

我们如果在另一个组件中也使用了store.state.count,可以发现count的值会被同步修改。

这就是Vuex的基础用法,和vue-router一样,我们明白原理和使用方法后,来尝试实现下简易版本的Vuex。

自定义Vuex的实现

学习过vue-router章节的同学应该都很清楚了,我们实现自定义的第一步就是根据暴露出来的方法来搭建基础的框架。

我们来新建一个newStore.js文件,在使用Vuex的过程中,我们一共使用了createStoreuseStore两个方法,createStore方法返回一个实例,所以newStore.js中有如下内容。

// 根据参数创建store实例
const createStore = (params) => {
    // 返回空的store实例
    return {

    }
}

// 返回store实例
const useStore = () => {
    
}

export {
    createStore,
    useStore
}

与createRouter方法一样,createStore方法在使用的时候传入的参数是个对象,并返回一个store实例,我们这里同样先返回一个空的对象。

我们看一下调用createStore方法时传入的参数长什么样:

createStore({
    state() {
        return {
            count: 0
        }
    },
    mutations: {
        add (state) {
            state.count++
        }
    },
})

传入的参数是个对象,对象中有state和mutations,所以我们将这两个内容都保存到我们的store实例中:

const createStore = (params) => {
    return {
        // 传入的state是个方法,我们真正需要的是返回的结果
        state: params.state(),
        mutations: params.mutations,
    }
}

上面我们说过要将一个响应式的数据绑定到全局,Vuex中的数据都保存在state中,所以我们的state必须是个响应式对象,params.state()返回的是个复杂数据类型,那么我们就使用reactive来定义:

const createStore = (params) => {
    return {
        // state声明成响应式
        state: reactive(params.state()),
        mutations: params.mutations,
    }
}

同样在app.use的时候,我们需要提供一个install方法,将store实例注册到全局。

import { reactive, provide, inject } from "vue";
...
const createStore = (params) => {
    return {
        state: reactive(params.state()),
        mutations: params.mutations,
        // 增加install方法
        install(app) {
            app.provide('STORE', this);
        }
    }
}

createStore方法我们基本上就实现了,还有个useStore方法,我们在组件中调用useStore方法时可以获取到store实例,而实例已经被我们注册到app.provide('STORE', this)全局了,那么使用inject去获取就可以了。

// 返回store实例
const useStore = () => {
    return inject('STORE');
}

使用useStore获取到实例后,我们还需要有个commit方法来更新数据store.commit('add'),所以我们实例下还有个commit方法,接收mutations下的方法名作为参数。

const createStore = (params) => {
    return {
        state: reactive(params.state()),
        mutations: params.mutations,
        // commit用来执行mutations下的方法
        commit(fun, payload) {
            // mutations下的方法接受state作为参数
            this.mutations[fun](this.state, payload);
        },
        install(app) {
            app.provide('STORE', this);
        }
    }
}

好了,简单版本的Vuex我们就已经完成了,完整代码如下:

import { reactive, inject } from "vue";

const createStore = (params) => {
    return {
        state: reactive(params.state()),
        mutations: params.mutations,
        // commit用来执行mutations下的方法
        commit(fun, payload) {
            // mutations下的方法接受state作为参数
            this.mutations[fun](this.state, payload);
        },
        install(app) {
            app.provide('STORE', this);
        }
    }
}

const useStore = () => {
    return inject('STORE');
}

export {
    createStore,
    useStore
}

我们修改下store/index.js文件和组件中对Vuex的引用,替换成newStore。

- import { createStore } from 'vuex';
+ import { createStore } from '../newStore';

...

这样我们就完成了自定义的Vuex,页面效果也与Vuex保持了一致,大家也可以自己去尝试下继续实现actions的功能。

Vuex的更多应用

除了上面的基本功能,Vuex还有更多更复杂的功能,我们在搭建Vue3项目的章节中提到过:

store是负责数据管理的地方。我们的业务逻辑都会在store中实现,比如业务数据接口的调用就会放在store中去做,组件内包含的都是与业务无关的内容。

与本节内容结合,store中保存的是与业务逻辑相关的数据,而与业务逻辑相关的请求,我们也在store中进行调用,与mutations下的方法不同的是,mutations下的方法都是同步执行,业务的接口请求是异步的,需要用到actions。

mutations: {
    set(state, count) {
        state.count = count;
    }
},
actions: {
    getCount ({commit}) {
        // 异步接口获取count值
    }
}

我们在mutations下新增一个set方法,用来设置count的值,actions下的getCount方法用来调用异步接口,我们使用promise模拟一个异步操作。

function getCountApi () {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(3)
        }, 2000);
    })
}
actions: {
    getCount ({commit}) {            
        getCountApi().then((res) => {
            commit('set', res)
        })
    }
}

actions不能直接操作数据,但是actions的参数解构可以得到commit方法,通过commit触发mutations下的set方法来更新数据,引用Vuex官网的规则如下:

  1. 提交 mutation 是更改状态的唯一方法,并且这个过程是同步的。
  2. 异步逻辑都应该封装到 action 里面。

可能有同学习惯在组件中去调用接口数据,拿到返回结果后直接渲染到页面上,有需要同步的数据再保存一份到store中,相比之下,将接口请求封装到store中,业务数据只保存在store中,组件里面只进行相关方法的调用会划分得更加清晰。

这样一来,业务数据不会与组件本身的数据混合在一起,并且只需要保存一份,组件只需要关注自己的逻辑,与业务方解耦。这两种模式大家可以认真的想一想,将业务请求放到store中还有什么其他的好处?

说回Vuex本身,我们将store与业务强关联了,而业务我们会根据需求进行模块的划分,比如一个商城的网页就会有用户模块、商品模块、订单模块等等,所以我们的store也需要进行模块的划分,与我们的业务模块相对应的进行数据管理。

那么我们怎么根据模块来管理store呢?

在Vuex中,提供了Module的概念,我们可以根据模块来创建store,比如我们同时存在用户模块和商品模块,需要如下创建store。

// useStore.js 用户模块
const useStore = {
    state: () => {
        return {
            name: '小明',
            age: 18
        }
    },
    mutations: { 
        changeUseName(state, name) {
            state.name = name;
        },
        changeUseAge(state, age) {
            state.age = age;
        }
    },
    getters: {
        birthday: (state) => {
            return new Date().getFullYear() - state.age
        }
    }
}

export default useStore;
// goodsStore.js 商品模块
const goodsStore = {
    state: () => {
        return {
            goodsName: '商品1',
            goodsCount: 10
        }
    },
    mutations: { 
        setGoodsCount(state, count) {
            state.goodsCount = count;
        }
    },
}

export default goodsStore

从上面的代码中,我们新增了两个store,在useStore中,存储了用户的姓名和年龄,提供了修改姓名和年龄的方法,还有一个新增的getters属性,getters的作用与computed类似,可以根据state中的值,返回计算后的响应式结果。goodsStore中则保存了商品相关的信息,这样在两个文件中,我们就分模块定义了store。

在index.js中引入各个模块:

  import { createStore } from 'vuex';

+ import useStore from './useStore';
+ import goodsStore from './goodsStore';

const store = createStore({
    state() {
        ...
    },
    mutations: {
        ...
    },
    actions: {
        ...
    },
    // 引入模块
    modules: {
      useStore,
      orderStore
    }
})

我们打印下store实例,看看引入的模块放在了哪里。

在这里插入图片描述

从控制台打印的结果可以看到,modules引入的模块与state下定义的变量在同一级,这里我们做个区分,在createStore下定义的变量和方法,我们划分到根store下,每个模块中定义的在模块store下,那我们要怎么调用模块store下的数据和方法呢?

获取模块store下的state需要携带上模块的名称:

store.state.[模块名称].[变量名称]
例:store.state.useStore.name

调用模块store下的getters、mutations和actions与调用根store一致就不多提了。

模块与根store的state在同一级中,如果模块与state存在同名情况会不会互相覆盖呢?

答案是会的,同名的模块会覆盖掉根state下的内容,我们可以通过添加命名空间来避免同名之间的覆盖。

const useStore = {
    // 添加命名空间
    namespaced: true,
    ...
}

添加了命名空间后,在访问模块getters、mutations和actions的时候,就需要添加名称来区分具体是哪个命名空间下的方法。

store.getters['useStore/birthday']
store.commit('useStore/name', '小红');
store.dispatch('useStore/xxx', '参数')

总结

本节中我们介绍了Vue3中的数据管理,首先大家需要区分数据类型,与外部业务无关,只关系到组件本身状态的数据放到组件内部,通过ref,reactive管理。与业务逻辑强关联,需要在多个组件中被使用的数据可以通过Vuex来进行管理。

然后我们介绍了Vuex的基本使用和原理,并手动实现了一个简单的Vuex,进一步加深对原理的理解。还说明了mutations和actions的使用原则,mutations中是对数据进行同步的操作,异步的操作要放到actions中,所以我们建议将业务请求的api都放到store中去处理,并说明了这种模式的优点。

最后结合实际的业务情形,对Vuex的扩展使用进一步介绍,丰富了Vuex在实战中的更多应用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/561960.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Imagination推出IMG CXM最小GPU,为家庭娱乐带来无比便捷的用户界面

全新IMG CXM GPU核兼容RISC-V并原生支持全HDR&#xff0c;帮助数字电视及整个消费市场降低成本 中国北京 - 2023年5月23日 - Imagination Technologies推出全新IMG CXM GPU系列为对成本敏感的消费级设备带来无缝的视觉体验。该系列包含原生支持全HDR用户界面的最小GPU。 IMG CX…

全面撤出印度市场,郭台铭终于明白了到底谁成就了富士康

郭台铭创立的富士康迅速发展成为全球最大的代工厂&#xff0c;它也因此成为制造业中的标杆&#xff0c;巨大的成绩让郭台铭开始有点飘飘然&#xff0c;以为富士康的成就完全是他的努力&#xff0c;然而随着手机组装业在印度遭受的挫折&#xff0c;郭台铭或许终于清醒认识到富士…

图的拓扑排序AOV网,有向无环图DAG描述表达式,关键路径AOE网。

一&#xff0c;有向无环图DAG描述表达式 1.DAG 若一个有向图中不存在环&#xff0c;则称为有向无环图&#xff0c;记为DAG。 2.用二叉树描述表达式 3.用DAG描述表达式 用二叉树描述表达式有缺点&#xff0c;有些结点大可不必存储&#xff0c;可以共用。 step1:把各个操作数…

【网络编程】| 搭建控制台通信demo

目录 &#x1f981;TCP通信实现原理&#x1f981;TCP单向通信创建服务端创建客户端 &#x1f981;TCP双向通信创建服务端创建客户端 &#x1f981;创建点对点的聊天应用创建发送消息线程和接收消息线程创建服务端创建客户端 &#x1f981;优化点对点聊天应用 &#x1f981;TCP通…

既然有了IP地址,为什么还需要MAC地址?两者到底有啥区别,深入分析后终于明白了!

在计算机网络中&#xff0c;IP地址和MAC地址是两个最基本的概念。IP地址在互联网中是用于标识主机的逻辑地址&#xff0c;而MAC地址则是用于标识网卡的物理地址。虽然它们都是用于标识一个设备的地址&#xff0c;但是它们的作用和使用场景是不同的。 IP地址是在网络层&#xff…

Windows下安装搭建MQTT服务器

服务器常用的有emqx,还有apache apolle&#xff0c;这里用的是emqx 服务端以及客户端可以使用MQTTnet&#xff08;NuGet包&#xff09; 一、MQTT服务器&#xff08; emqx &#xff09;搭建 1. 下载服务器MQTT Broker 从 https://www.emqx.io/cn/mqtt/public-mqtt5-broker下载…

Wi-Fi 6为啥那么快?

Wi-Fi 6&#xff08;802.11ax&#xff09;是当前最新的无线局域网标准&#xff0c;它带来了许多重要的改进和性能提升。本文将详细介绍Wi-Fi 6的速度和性能&#xff0c;以及与前一代标准相比的差异。 Wi-Fi 6的速度提升 相比于前一代标准&#xff08;Wi-Fi 5或802.11ac&#x…

fNIRS最新研究 | 脑间和脑内耦合对运动同步的差异贡献

导读 人类大脑支持行为的一个基本特征是它在脑区之间建立连接的能力。一种有前景的方法认为&#xff0c;在社交行为中&#xff0c;大脑区域不仅与大脑内的其他脑区建立连接&#xff0c;而且还与互动伙伴的其他脑区协调活动。本文探索脑间和脑内耦合是否对运动同步有不同的贡献…

设计模式之【责任链模式】,路铺好了,你走不走?

文章目录 一、什么是责任链模式1、状态模式与责任链模式的区别2、责任链模式使用场景3、责任链模式的优缺点4、责任链模式的角色 二、实例1、责任链模式的一般写法&#xff08;1&#xff09;一般写法&#xff08;2&#xff09;一般写法-使用建造者模式进阶&#xff08;3&#x…

nacos配置加载顺序

spring boot 调整日志打印情况 logging:level:com:alibaba:cloud: debug # nacos: debugorg:springframework:context: debugcloud: debug # boot: debug 项目启动时打印了 properties search order:PROPERTIES->JVM->ENV->DEFAULT_SETTING 查看具体代…

股票配资交易系统【实盘】

股票配资系统建设&#xff0c;本文档主要针对实盘股票配资系统。 股票配资交易系统主要包含三部分&#xff1a;App客户端、交易程序服务端、管理后台 App客户端 app客户端是原生应用&#xff0c;非H5生成。客户端主要功能是承接用户的股票订单委托、查询、用户资金转入&#x…

2023.5.12解决Ubuntu中ens33没有ip

在Ubtuntu中的ens33没有ip 如果Ubuntu版本过高 sudo netplan apply如果是Ubuntu 16.04及更早版本 sudo vi /etc/systemd/resolved.conf具体情况如下图所示 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000link/loopbac…

java版企业工程项目管理系统 Spring Cloud+Spring Boot+Mybatis+Vue+ElementUI+前后端分离 功能清单

Java版工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离 功能清单如下&#xff1a; 首页 工作台&#xff1a;待办工作、消息通知、预警信息&#xff0c;点击可进入相应的列表 项目进度图表&#xff1a;选择&#xff08;总体或单个&#xff09;项目显示…

iVX和其它低代码的平台的区别,“低代码/无代码”分三类

前言 这个图非常认真地对整个“低代码”领域做了严格的分类&#xff0c;这个里面并没有把只针对工作流或单纯BI的工具产品划分进去&#xff0c;主要是这一类&#xff0c;在我个人理解里面更像“SaaS”&#xff0c;也就是增强版SaaS产品&#xff0c;这类产品也主要是给业务人员使…

SpringCloud学习-实用篇04

以下内容的代码可见&#xff1a;SpringCloud_learn/day04 1.初始MQ 同步通讯和异步通讯 微服务间通讯有同步和异步两种方式&#xff0c;同步通讯就像打电话需要实时响应&#xff0c;异步通讯就像发邮件不需要马上回复。两种方式各有优劣&#xff0c;比如打电话能立即得到响应&a…

STM8使用pwm接口调试GDS06灰尘传感器

背景 刚好有项目使用GDS06这款传感器&#xff0c;这里简单做个记录。 GDS06接口如下&#xff0c;这里支持串口和PWM的输出到MCU&#xff0c;由于项目采用STM8S003F3P6&#xff0c;资源极其有限。 所以硬件设计的时候&#xff0c;就考虑采用PWM的接口方式&#xff0c;这样只是…

安科瑞有源电力滤波器的设计原理及应用前景

安科瑞 徐浩竣 江苏安科瑞电器制造有限公司 zx acrelxhj 摘要&#xff1a;该文介绍了有源电力滤波器的工作原理和基本控制方法&#xff0c;并阐述有源电力滤波器的现状及发展前景等等。 关键词&#xff1a;有源电力滤波器&#xff1b;谐波&#xff1b;工作原理&#xff1b;…

方案绞尽脑汁想不出?试试这款AI代写方案

一份计划方案&#xff0c;往往是工作进行下去的核心环节&#xff0c;需要考虑很多因素和变量&#xff0c;在某些情况下&#xff0c;可能没有足够的信息来制定有效的方案。这可能会导致需要额外的研究和调查&#xff0c;以便了解更多关于问题的信息&#xff0c;这将延长制定方案…

侧边拖拉功能

一、页面 <div class"resize-handle" mousedown"startResizing">⋮</div> 二、js data() {return {showSideBar: true,leftPaneWidth: 63, // 左侧区域的初始宽度isResizing: false, // 标记是否正在调整大小startX: 0, // 调整大小开始时的…

Spring AOP 中的切点是什么?如何定义切点?

Spring AOP 中的切点是什么&#xff1f;如何定义切点&#xff1f; 什么是切点&#xff1f; 在 Spring AOP 中&#xff0c;切点&#xff08;Pointcut&#xff09;是指一组连接点&#xff08;Join Point&#xff09;的集合。连接点是程序执行过程中的某个特定点&#xff0c;例如…