如何优雅使用 vuex

这篇具有很好参考价值的文章主要介绍了如何优雅使用 vuex。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

大纲

本文内容更多的是讲讲使用 vuex 的一些心得想法,所以大概会讲述下面这些点:

Q1:我为什么会想使用 vuex 来管理数据状态交互?

Q2:使用 vuex 框架有哪些缺点或者说副作用?

Q3:我是如何在项目里使用 vuex 的?

初识 vuex

对于 vuex,有人喜欢,有人反感

喜欢的人觉得它可以很好的解决复杂的数据交互场景

反感的人觉得它有各种缺点:繁琐冗余的代码编写、维护性差的字符串形式变量注入、过于依赖 vue 框架导致异步扩展场景差

这其中,有一个很模糊的点,复杂的数据交互场景并没有一个衡量标准,每个人都有自己的见解

再加上不同人有着不同的项目经历,这就造成了经常会出现有趣的现象:你体会不到我为什么非要使用 vuex,他理解不了这种场景何须使用 vuex,我也讲不明白选择 vuex 的缘由

借用官网文档一句话:

您自会知道什么时候需要它

很玄乎,更通俗来讲就是,多踩点坑,多遭遇些痛点,当你的最后一根稻草被压垮时,自然就会去寻找更好的方案解决

我一直都不喜欢 vuex,因为我觉得它的 mapMutations 或者 mapState 注入到 vue 里的变量和方法都是字符串,极大的破坏了代码的可读性和维护性,没办法通过 idea 快速的跳转到变量定义的地方

当然,你也可以定义一些静态变量来替换这些字符串就可以解决跳转问题,但代价就是代码更繁琐了,本来使用 vuex 时就需要写一堆繁琐的代码,这下更麻烦

还有一个不想使用 vuex 的原因是因为我的项目业务逻辑挺复杂,除了 vue 单文件外,项目里还划分了来负责业务逻辑或异步任务的 js 层代码,而 vuex 是为 vue 框架而设计的,存放在 vuex 数据中心的变量可以通过它的一些工具方法来注入到 vue 组件的 computed 计算属性里方便直接使用,比如

import { mapState } from 'vuex'

export default {
  // 映射 this.count 为 store.state.count
  computed: mapState({
    count: state => state.count
  })
}

但如果想在 js 文件里使用 vuex 里的数据,就会比较繁琐:

import store from 'store'

console.log(store.state.count);

基于以上种种原因,我迟迟未在项目里使用 vuex

那么,我最后为什么又选择使用了 vuex 呢?

一,项目的一些数据交互场景使用 vue 原生的输入输出方案已经忍不下去了

二,我想办法解决了我没法忍受的 vuex 的几个缺点了

三,这是个新项目,并没有复杂的业务场景,项目基本由 vue 单文件来书写即可

简单来说,就是有个新项目符合适用 vuex 的场景,而且一些组件交互场景使用原生方案过于繁琐,vuex 刚好能够解决这个问题,虽然 vuex 有一定的使用成本,但这些缺点恰好又被我想了一些法子解决或简化掉

这样一来,引入 vuex 既能解决我的诉求,又不会引入太多我无法接受的缺点,那自然可以来玩一玩

背景

vue 框架是基于组件机制来组装成页面,所以页面数据是分散到各个组件间,而组件间的数据交互使用的是 vue 自带的输入(props)输出($emit)机制

这种数据交互方案有个特点,数据对象都存储在组件内部,交互需要通过输入和输出

而输入输出机制有个缺点:页面复杂时,经常需要层层传递数据,因为非父子组件间的交互,只能寻找最近的祖先组件来做中转

同时,输入输出机制还有一个局限性:当不同页面的组件需要进行数据交互时,它就无能为力了

平常开发,这种输入输出的方案也基本满足了

但如果有需要跨页面的数据交互或者说,有需要将数据做持久化处理的场景时;以及如果页面组件层次复杂,存在props 和 $emit 层层传递时,那这时候如果忍不了输入输出方案的用法,那么就可以研究新方案了

解决这种场景的方案很多,但从本质上来讲,都可以统归为:数据中心方案

这种方案思路就是将数据对象从组件内部移出到外部存储维护,需要使用哪个数据变量的组件自行去数据中心取

vue 其实也有机制可以达到这种效果,如:依赖注入,但慎用,太破坏数据流的走向了

我们也可以自己用 js 来实现一个数据中心,专门建个 js 文件来存储维护数据模型,需要数据变量的组件引入 js 文件来读取

但每个数据中心都必须解决两个问题:数据复用和数据污染,通俗来讲就是数据初始化和重置,也就是数据的生命周期

数据复用是为了确保不同组件间从数据中心里读取时,是同一份数据副本,这才能达到数据交互目的

而数据污染是指不同模块间使用同个数据中心时,数据模型是否可以达到相互独立,互不影响的效果,这通常是某个功能在不同模块间被复用时会出现的场景;如果这种场景不好理解,那么也可以想想这种场景:再次加载该页面,组件再次被创建后,从数据中心里读取的数据副本是否是相互独立的

如果数据存储在 vue 组件内部,那数据的生命周期就是跟随着组件的创建和销毁,这也是为什么 data 是一个返回对象的函数,因为这样可以借助 js 的函数作用域机制来解决数据的复用和污染问题

但数据从 vue 组件内部移出,存储到数据中心,那么这些处理就需要自己来实现

所以,数据中心并不是简单建个 js 类,里面声明下数据对象就完事了的

基于此,我选择使用 vuex

vuex 副作用

先看个使用 vuex 的简单例子:

// 声明
const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    increment (state) {
      // 变更状态
      state.count++
    }
  }
})

// vue 里使用
import { mapMutations } from 'vuex'
import { mapState } from 'vuex'

export default {
  // ...
  computed: {
     ...mapState({
         // 将 `this.count` 映射为 `this.$store.state.count`
         count: state => state.count
     })   
  },
  methods: {
    ...mapMutations([
      'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`
    ])
  }
}

仅仅简单定义个数据对象,就需要声明对象模型 state,声明对象操作方法 mutation,然后在相应的 vue 组件内先通过 mapState 注入变量的读方法,再通过 mapMutations 注入变量的写方法

而以上这么多繁琐的代码,在原本的 vue 机制里,就是简单的在 data 里声明下变量就完事,这一对比,vuex 的使用上,复杂度和繁琐度很大,有一定的使用成本

所以很多人不喜欢用它,官方也说简单的页面也没有必要去使用它

这是我觉得 vuex 的第一个缺点,或者说副作用:繁琐冗余的代码编写

第二个我觉得 vuex 的缺点就是,mapState 或 mapMutation 注入的变量,都是字符串的

字符串就意味着,你在 vue 单文件内其他地方通过 this.xxx 使用这些变量时,当你想查看变量的声明时,idea 无法识别!

这是我特别无法接受的一点,降低我的维护、开发效率

不过这点因人而异,有人觉得它不是个问题,或者使用个静态变量来替换字符串也可以解决,但这些我个人是没办法接受

然而 vue 原生输入输出的数据交互又不足够支撑我的一些需求场景,自己用 js 实现个数据中心吧,又担心没强制规范,没处理好,后期跑偏掉更难维护,那就想想办法搞定 vuex 的这两个缺点吧

如何更简易的使用 vuex

先说下,我虽然用了些方法,让我使用 vuex 可以达到我的预期,既满足我的需求场景,又不至于引入太多副作用

但实际上,这种方式也许就偏离了 vuex 官方的推荐方式了,别人不一定能接受我的这种用法

所以,这篇更多的是分享我的一些思路和想法,有一说一,并不通用,欢迎拍砖

就我个人对于 vuex 的缺点,我所不能接受的就两点:

  • 繁琐冗余的代码编写
  • 维护性差、可读性差的字符串变量注入

那么,就是得想办法解决这两个问题,先来说第一个

封装自动生成代码解决 vuex 使用繁琐问题

用 vuex 需要编写很多繁琐的代码,这些代码是少不了的,既然少不了,那换个思路,不用我来编写不就好了

想办法提取共性,封装个工具方法,让它来生成每次使用 vuex 的那些繁琐代码,这样一来,使用就方便了

state 里声明的数据对象模型,这些代码是没办法自动生成的,毕竟数据模型都不大一样

而修改数据变量的 mutation 代码就可以想办法来自动生成了

/**
 * 根据 state 对象属性自动生成 mutations 更新属性的方法,如:
 * state: {
 *  projectId: '',
 *  searchParams: {
 *      batchId: ''
 *  }
 * }
 *
 * ===>
 *
 * {
 *  updateProjectId: (state, payload) => { state.projectId = payLoad }
 *  updateSearchParams: (state, payload) => { state.searchParams = {...state.searchParams, ...payload} }
 *  updateBatchId: (state, payload) => { state.searchParams.batchId = payload }
 * }
 *
 * 非对像类型的属性直接生成赋值操作,对象类型属性会通过扩展运算符重新生成对象
 * 且会递归处理内部对象的属性,扁平化的生成 updateXXX 方法挂载到 mutations 对象上
 * @param {Object} stateTemplate
 */
export function generateMutationsByState(stateTemplate) {
  let handleInnerObjState = (parentKeyPath, innerState, obj) => {
    Object.keys(innerState).forEach(key => {
      let value = innerState[key];
      let updateKey = `update${key[0].toUpperCase()}${key.substr(1)}`;
      if (typeof value === 'object' && value != null && !Array.isArray(value)) {
        obj[updateKey] = (state, payload) => {
          let target = state;
          for (let i = 0; i < parentKeyPath.length; i++) {
            target = target[parentKeyPath[i]];
          }
          target[key] = { ...target[key], ...payload };
        };
        handleInnerObjState([...parentKeyPath, key], value, obj);
      } else {
        obj[updateKey] = (state, payload) => {
          let target = state;
          for (let i = 0; i < parentKeyPath.length; i++) {
            target = target[parentKeyPath[i]];
          }
          target[key] = payload;
        };
      }
    });
  };
  let mutations = {};
  Object.keys(stateTemplate).forEach(key => {
    let obj = {};
    let value = stateTemplate[key];
    let updateKey = `update${key[0].toUpperCase()}${key.substr(1)}`;
    if (typeof value === 'object' && value != null && !Array.isArray(value)) {
      obj[updateKey] = (state, payload) => {
        state[key] = { ...state[key], ...payload };
      };
      handleInnerObjState([key], value, obj);
    } else {
      obj[updateKey] = (state, payload) => {
        state[key] = payload;
      };
    }
    Object.assign(mutations, obj);
  });
  return mutations;
}

然后是 mapState 和 mapMutation 注入到 vue 组件的这些代码也可以通过 computed 计算属性的特性来自动生成,这样使用上更加方便,毕竟使用 computed 计算属性的方式就跟使用 data 里声明的变量一样,没有什么区别

import store from './index';
/**
 * 将 store 里指定的 state 转成计算属性 computed 的 set() get()
 * vue 里就可以直接类似操作 data 属性一样使用 state
 *
 * @param {String} moduleName state 所属的 store 的 module 名
 * @param {Array} states 待处理的 states e.g: ['project', 'searchParams.projectName'] 其中,
 * 挂载在 computed 上的属性名,默认等于 state,当 state 结构多层时,取最后一层的属性名
 *
 * ps: state 对应的 mutation 必须以 updateXXX 方式命名
 */
export function storeToComputed(moduleName, states) {
  if (!store) {
    throw new TypeError('store is null');
  }
  if (!moduleName) {
    throw new TypeError("state's module name is null");
  }
  if (!states || !Array.isArray(states) || states.length === 0) {
    throw new TypeError('states is null or not array');
  }
  let computed = {};
  states.forEach(state => {
    if (state.indexOf('.') !== -1) {
      let _states = state.split('.');
      let _key = _states[_states.length - 1];
      computed[_key] = {
        get() {
          let res = store.state[moduleName];
          for (let i = 0; i < _states.length; i++) {
            res = res[_states[i]];
          }
          return res;
        },
        set(value) {
          store.commit(
            `${moduleName}/update${_key[0].toUpperCase()}${_key.substr(1)}`,
            value
          );
        },
      };
    } else {
      computed[state] = {
        get() {
          return store.state[moduleName][state];
        },
        set(value) {
          store.commit(
            `${moduleName}/update${state[0].toUpperCase()}${state.substr(1)}`,
            value
          );
        },
      };
    }
  });

  return computed;
}

那么最终可以达到的效果就是:

  • 只需在 store 文件里声明 state 数据变量
  • 然后再需要注入的 vue 组件里注入即可
// 声明
import { generateMutationsByState } from './helper';

const global = {
    state: {
        count: 0
    }
}
global.mutations = generateMutationsByState(global.state);

const store = new Vuex.Store({
    modules: {
        global
    }
})
// vue里使用
import { storeToComputed } from '@/store/storeToComputed';

export default {
  // ...
  computed: {
      // 将 this.$store.state.global.count 映射成 this.count
     ...storeToComputed('global', ['count'])
  },
}

我的这种用法,其实就只是单纯将 vuex 拿来作为数据中心使用而已,在 store 文件里不编写逻辑代码,也不使用 action

这种用法的好处,我是觉得,会跟原本在 vue 的 data 里声明变量后的用法比较类似。因为就是将原本定义在 data 里的变量换成定义在专门的 store 文件里,然后再多一步将变量通过工具方法注入到 vue 的 computed 里,接下去的使用变量的任何场景,在哪赋值,在哪取值,哪里处理异步请求等等的代码,原本怎么写,现在还是怎么写,完全不影响

这就意味着,这种方案后续如果有缺陷,或者用不习惯,那么想切换到 vue 原生的输入输出方案非常方便,影响点、改动点都会比较少,就是将 storeToComputed 注入到 computed 的变量换到 data 就完事了

甚至说,后续想换掉 vuex 也会比较方便,毕竟只是单纯用它当做数据中心而已

然后再配合上 vuex 的动态挂载和卸载的用法,这个数据中心就可以像 angular 框架那样做到精确控制数据对象的作用域和生命周期,全局共享、模块间共享、页面内共享、组件内共享等都可以很方便做到,这样一来,数据交互就不怕复杂场景了

这是我之所以会这么使用 vuex 的考虑

自定义 vscode 插件解决字符串变量的跳转问题

繁琐的代码编写问题搞定了,接下去就是看看怎么解决字符串变量注入的跳转问题了

先来说说,我为什么会在意变量支不支持利用 idea 直接跳转到声明的地方

这是因为,有些页面比较复杂,数据变量比较多,或者时间久了,很容易忘记一些变量的命名、含义

而我们通常都只会在声明的地方加上一些注释

所以利用 idea 直接快速跳转到声明的地方,第一,有注释可以快速帮助回忆、理清变量含义;第二,忘记变量命名全称可以快速复制使用;第三,方便我查看其它数据变量

那么,怎么解决这个问题呢?

自然就是自己扩展开发个 vscode 插件来支持了,面向百度的话,vscode 插件开发并不困难,看几篇教程,清楚插件的生命周期和一些 API 就可以上手了

关键是,如何识别 vuex 注入的这些变量?如何跳转到 store 文件里声明数据变量的 state 位置?

如果想做成通用的插件,那可能需要多花点精力

但如果只是基于自己当前的项目来解决这个问题,那就简单多了,因为项目有一定的规范,比如 vuex 的 store 文件存放的目录地址,比如注入到 vue 组件里的使用方式,这些都是有规范和规律的

简单说下我的思路:

  1. 先扫描项目 store 目录下文件,识别出有数据模型 (state) 的文件,解析并存储数据模型各个变量名和位置
  2. 注册 vscode 的变量操作响应,当按住 ctrl 并将鼠标移到变量上时,响应我们的插件程序
  3. 判断当前聚焦操作的变量是否是通过在 computed 里注入的变量,是则继续下一步寻找变量声明的文件位置
  4. 通过变量名和模块名到 store 里匹配变量,匹配到后,记录变量的声明信息和文件位置,当点击左键时,响应跳转行为

github 地址:vuex-peek

总结

最后简单总结下,项目里并不是必须要使用 vuex,vuex 所解决的场景,用 vue 原生的输入输出机制想想办法也能解决,区别可能就是代码的可读性、维护性上的区别,数据流走向是否清晰等

vuex 作为三方库,自然就是一个可选的服务,用不用,怎么用,都因人而异;考虑好自己的诉求,对比好引入前后的影响点,权衡好自己能接受的点就好

比如我,使用 vuex 的方式上说得难听点,也有点不伦不类,毕竟并没有按照官方示例来使用,反而是自己搞了套使用规范,这也增加了别人的上手成本

所以写这篇,不在于强推使用 vuex,只是从自己的一些经历分享自己使用一些三方库的心路历程,所思所想

很多时候,当你开始吐槽某某方案、当你开始无法接受某某用法时,这其实意味着,这是一次绝佳的探索机会

吐槽完就想办法去优化、去寻找新方案;接受不了时,就想办法去研究看能否解决这些痛点

人嘛,总是在一次次的踩进坑里,再爬出来文章来源地址https://www.toymoban.com/news/detail-746228.html

到了这里,关于如何优雅使用 vuex的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 如何优雅地使用 Markdown?

    程序员宝藏库 :https://gitee.com/sharetech_lee/CS-Books-Store 要想优雅,首先得有一个丝滑、好用的Markdown编辑器。 我用typora做笔记三年多,收费之后我开始找替代品,尝试了很多,总是有这样那样的问题不满意,有习惯问题,也有编辑器本身问题。 但是,我最终还是找到了2款无论

    2023年04月10日
    浏览(33)
  • 前端如何优雅地使用枚举

    枚举(Enumeration)是一种常见的编程数据类型,它用于表示一组有限的取值。在前端开发中,枚举可以用于定义常量、选项等,有助于提高代码的可读性和可维护性。本文将介绍前端如何优雅地使用枚举。 在JavaScript中,枚举并不是一种原生数据类型,但可以使用对象或常量来

    2023年04月17日
    浏览(32)
  • 如何优雅的使用后端接口

    一个后端接口大致分为四个部分:接口地址(url)、接口请求方式(get、post等)、请求数据(request)、响 应数据(response)。 一、URL Method Rest 设计风格 ===》 Restful API 简单理解: URI 是用来唯一标志一个互联网资源;Method 是用来标识当前请求对该资源进行什么操 作。 版本号、状态码、

    2024年02月14日
    浏览(28)
  • 如何优雅的使用ChatGPT指令,指令大全

    我希望你充当Linux终端。我将键入命令,您将回复终端应显示的内容。我希望你只回复一个唯一代码块中的终端输出,没有别的。不要写解释。除非我指示你这样做,否则不要键入命令。当我需要用英语告诉你一些事情时,我会把文本放在大括号里{像这样}。我的第一个命令是

    2024年02月05日
    浏览(61)
  • [阿里云堡垒机]如何优雅使用SCP

    最近公司在服务器前面加了一个堡垒机,并且禁用了服务器的SSH外网访问权限。开始的时候都是那么人畜无害,直到有一天需要更换ssl证书的时候,发现scp命令没有办法使用。最后临时的方案是上传到文件服务器,然后在下载。 请确保个人电脑与堡垒机网络连接正常且可以正

    2024年01月22日
    浏览(33)
  • 技术扫盲:如何优雅的使用 java -jar

    java -jar xxx.jar java -jar 是一个用于在命令行界面中执行 Java 可执行 JAR 文件的命令。它的语法如下: 其中: java 是 Java 运行时环境的可执行文件。 -jar 是一个选项,表示要执行的文件是一个 JAR 文件。 JAR 文件路径 是要执行的 JAR 文件的路径。 [参数] 是可选的命令行参数,用于

    2024年02月03日
    浏览(36)
  • 如何优雅地使用Low Code提高开发效率

    2023年,低代码热度有,但是在企业内部核心场景的落地比例不高,推进进展也没有想象中快。就算是这样,低代码赛道也在“暗流涌动”。 数字化趋势下,很多企业想要以数字化的手段进行降本增效 。很多企业希望以低代码的模式,搭建符合自身业务需求的应用。 现在国内

    2024年02月09日
    浏览(64)
  • 【Ubuntu】如何使用命令行(优雅地)安装/卸载Microsoft Edge

    把Ubuntu从18.04更新到22.04以后,尽管电脑连接着WIFI,但是Edge死活访问不了网页,让我一气之下打算把Edge卸载重装。 使用如下命令卸载Edge 接着,使用如下命令将Edge的设置和配置文件一起删除 (可能如果安装的beta版本,就把命令行中的 dev 改为 beta ?不确定,可以试试看。)

    2024年02月17日
    浏览(76)
  • [toolschain] 怎么运用git 嵌套git 管理(子文件夹中也有个git) 并且如何简单设置使用repo的笔记 本文是求助GPT的记录 实践有用

    Q:一个文件夹a,a中的文件被a中的git 1管理,同时与a平级有一个git2,怎么让git 也能管理到git1 的内容 A:如果你想让一个 Git 仓库(git2)也能管理另一个 Git 仓库(git1)的内容,你可以使用 Git 的子模块(submodule)功能。子模块允许一个 Git 仓库包含另一个 Git 仓库,使得你可

    2024年02月04日
    浏览(61)
  • Vue3 如何优雅的使用 createApp 自定义通用Dialog

    最近在做一个项目的技术栈升级,从Vue2升级至Vue3,Vue2中有一个通用的全局 Dialog 方法,是通过 Vue.extend 来实现的,具体请看下方的 Vue2 代码: 一、main.js 中定义通用方法 二、定义通用 DialogLayout.vue 三、 定义需要通过 Dialog 打开的具体页面 四、具体使用 五、如何用 Vue3 的语

    2024年01月18日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包