Vuex状态管理的方法是什么
这篇文章主要介绍"Vuex状态管理的方法是什么",在日常操作中,相信很多人在Vuex状态管理的方法是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答"Vuex状态管理的方法是什么"的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
EventBus 事件总线
(图2)
一图胜千言,如图 2 。当然我们想到的最简单的方案,通过实现一个中心化的事件处理中心,来记录组件内的订阅者,当需要协同时就通过自定义事件通知到各个相关的组件内部的订阅者。当然通知中可以携带 payload 参数信息,达到数据共享的目的。其实 Vue 本身也自带一个自定义事件系统, Vue 组件之间的自定义事件就是基于此来实现,详细 api 请参与 Vue 文档。我们可以基于 Vue 本身实现 EventBus 的机制,不需要引入新的依赖,减少 bundle 体积,api使用如下述代码。
const vm = new Vue()// 注册订阅者vm.$on('event-name', (payload) => {/*执行业务逻辑*/})// 注册订阅者,执行一次后自动取消订阅者vm.$once('some-event-name', (payload) => {/*执行业务逻辑*/})// 取消某事件的某个订阅者vm.$off('event-name',[callback])// 通知各个订阅者执行相对应的业务逻辑vm.$emit('event-name',payload)
1、架构上的优点
在实践中发现基于 EventBus 的数据状态管理模式的优点:
代码的实现比较简单,设计方案容易理解
很轻量的方式就可以完成组件之间的解耦,将组件之间的强耦合变成对 EventBus 的弱耦合。
2、实践中的痛点
当然EventBus方案的也会有些不足:
因为业务逻辑分散在多个组件订阅者中,所以导致业务逻辑的处理变得碎片化,缺乏连贯的上下文。
在阅读和维护代码时,需要在代码中不断去寻找订阅者,导致业务流程理解上的中断和注意力的分散。
3、反思改进
在认识到 EventBus 的架构设计上的不足时,我们也会 Eating our own dog food,实现了一套可视化的机制,通过对代码的抽象语法树的分析,提取订阅者和发送者的信息,可视化显示他们之间的关联关系,帮助我们快速理解问题。
另外,对于复杂的业务逻辑设计出【前置脚本】的改进方案。例如,活动页面虽然是由多个RSC组件构成,但是请求的服务端接口还是一个,包含了页面初始化状态的所有的数据,此时我们就可以在前置脚本中统一处理获取数据的逻辑,然后再同步到各个RSC组件内部。【前置脚本】的方式,就是抽取一个全局的对象,包含共享的状态和业务逻辑。多个组件依赖这个全局的对象,架构设计如图3,是对 EventBus 方案的一个补充。
(图3)
4、总结
通过前置脚本,可以解决复杂业务难以维护理解的问题,但是也带来一些风险点如需要暴露全局对象,有被覆盖或者被修改的风险。经过前置脚本的改进之后,我们越来越清晰的感受到我们需要的状态管理模式是什么样子,那就是 Vuex 。那接下来我们就聊聊Vuex。
Vuex 状态管理
1、背景
Vuex 是什么?
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。
Vuex 有哪些特点?
集中式的组件状态管理,支持动态注册 store
与 Vue 的匹配度高,底层基于 Vue 的响应式数据特性来实现,保持了和 Vue 一样的数据处理特点
熟悉 Vue 后可以快速上手 Vuex ,学习成本比较低
完善的开发体验,官方的 devtools 和 time-travel 调试等,帮助开发者梳理数据可预测的变化
2、在平台引入对 Vuex 的支持
Vuex 是一个通用状态管理框架,怎么无缝融入到我们的 RSC 组件体系中呢?我们需要在项目中引入对 Vuex 的依赖和支持,在顶层的 Vue 中添加对 store 的依赖。
我们项目的基本结构:
.└── src ├── App.vue ├── app.less ├── assets ├── component ├── directive ├── main.js ├── stat ├── store └── utils├── babel.config.js├── package.json├── public
2.1 添加依赖
根据规范,首先在我们的项目目录中的 package.json 中添加对 Vuex 的依赖
{ "dependencies": { "vuex": "^3.0.1" }}
2.2 创建 store 对象
//store/index.jsimport Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)export const store = new Vuex.Store({ // 状态数据 state() { return {} }, getters: {}, mutations: {}, actions: {},})
2.3 顶层 Vue 对象注入 store
将上述创建 store对象注入到顶层的 Vue 对象中,这样所有的 Vue 组件就会通过 this.$store 获取顶层的 store 对象。另外, Vuex 还提供了好用的工具类方法 ( mapState , mapActions , mapGetters , mapMutations ) 来进行数据共享和协同。
// App.vueimport { store } from './store'new Vue({ // 注入 store // 在所有的改 Vue 管理的 vue 对象中都可以通过 this.$store 来获取 store,})
3、使用 Vuex 开发 RSC 组件
3.1 RSC 自有 store
我们还是希望在开发组件时,开发者大部分时间只关注自己的展现和业务逻辑,只是在组件在活动页中被渲染时,才将自身状态共享到顶层的 store 中去。所以组件具有自身的独立 store 状态管理,通过 namespace 命名空间进行模块的状态隔离,然后在组件的 beforeCreate 生命周期方法内,通过 Vuex 的 registerModule 进行动态的 store 的注册。
3.2 StoreMixin 注入
可以通过抽取公共 StoreMixin 来简化这一过程,还可以自动开启 namespaced: true 和针对当前的命名空间扩展快捷方法和属性。代码如下:
// store-mixn.jsexport default function StoreMixin(ns, store) { return beforeCreate() { // 保证 namespace 唯一性 // 开发者可以通过函数生成唯一的namespace // 框架可以生成唯一的namespace const namespace = isFn(ns) ? ns(this) : gen(ns) this.$ns = namespace store.namespaced = true this.$store.registerModule(namespace, store) // 扩展快捷方法和属性 this.$state = this.$store.state[namespace] this.$dispatch = (action, payload) => this.$store.dispatch(`${namespace}/${action}`, payload) this.$getter = //... this.$commit = //... }}
//store.js// 当前组件自有storeexport default { // 组件自身的状态 state() { return {} }, mutations: {}, getters: {}, //...other things}// code.vue// 组件对外的入口模块import store from './store'export default { mixins: [StoreMixin(/*namespace*/ 'hello', /* 组件的 store */ store)],}
3.3 命名空间冲突,怎么解?
因为在一个活动中 RSC 组件会被重复加载多次,所有也会导致相同 namespace 的 store 模块重复加载导致模块覆盖。怎么保证 namespace 的唯一性呢?我们可以,在 StoreMixin 中进行 namespace 注册的时候,判断有没有相同的 namespace ,如果有就对 namespace 做一次重命名。比如在已经注册了 hello 为命令空间的 store 时,再次注册 namspace hello 自动会变成 hello1 ,自动做区分。简单的算法实现如下,
// gen.js// 生成唯一的 namespaceconst g = window || globalg.__namespaceCache__ = g.__namespaceCache__ || {}/** * 生成唯一的 moduleName, 同名 name 默认自动增长 * @param {*} name */export default function genUniqueNamespace(name) { let cache = g.__namespaceCache__ if (cache[name]) { cache[name].count += 1 } else { cache[name] = { count: 0, } } return name + (cache[name].count === 0 ? '' : cache[name].count)}
另外,开发者可以通过 store-mixin 中传递自定义函数来生成唯一的 namespace 标识。比如,如下代码,根据 vue-router 中的路由动态参数来设置 namespace
export default { mixins: [StoreMixin((vm) => vm.$router.params.spuId), store],}
3.4 动态命名空间的挑战
因为动态 namespace 就会带来不确定性的问题,如下代码示例,假如hello被重命名为hello1, 另外在 Vuex 中 mapXXX ( mapState , mapMutations 等)方法时,需要精确传递 namespace 才能获取组件内 store 的上下文。
// code.vueexport default { mixins: [StoreMixin('hello', store)], computed: { ...mapGetters('hello', [ /* hello namespace store getter */ ]), ...mapState('hello', [ /* hello namespace state property */ ]), }, methods: { ...mapActions('hello', [ /* hello namespace actions method */ ]), ...mapMutations('hello', [ /* hello namespace mutations method */ ]), },}
3.5 扩展 Vuex 支持动态命名空间
怎么解决 Vuex mapXXX 方法中动态 namespace 的问题?首先我们我们想到的是在 StoreMixin 中将 namespace 设置在 Vue 的 this.$ns 对象上,这样被 StoreMixin 混入的组件就就可以动态获取 namespace 。
// store-mixn.jsexport default function StoreMixin(ns, store) { return beforeCreate() { // 保证 namespace 唯一性 const namespace = gen(ns) // 将重命名后的 namespace 挂载到当前 vue 对象的$ns 属性上 this.$ns = namespace //... }}
虽然我们可以在组件内通过 this.$ns 获取组件中的 store 的命名空间,假想着我们可以:
// code.vueexport default { computed: { ...mapGetter(this.$ns, [ /* hello namespace store getter */ ]), ...mapState(this.$ns, [ /* hello namespace state property */ ]), }, methods: { ...mapActions(this.$ns, [ /* hello namespace actions method */ ]), ...mapMutations(this.$ns, [ /* hello namespace mutations method */ ]), },}
很遗憾,在这个时刻 this 根本就不是当前 Vue 的实例,this.$ns 华丽丽的 undefined。那怎么办呢?JS 有很多函数式编程的特点,函数也是值,可以作为参数等进行传递,其实函数除了具有值特性外还有一个很重要的特性就是 lazy computed 惰性计算。基于这样的思考,对 mapXX 方法进行扩展,支持动态的 namespace 。然后在 mapXXX 方法中,等到 vm 是当前 Vue 的组件实例时,才去获取当前的组件的 namespace 。
// code.vueimport { mapGetters, mapState, mapActions, mapMutations } from 'vuex-helper-ext'export default { computed: { ...mapGetters((vm) => vm.$ns, [ /* hello namespace store getter */ ]), ...mapState((vm) => vm.$ns, [ /* hello namespace state property */ ]), }, methods: { ...mapActions((vm) => vm.$ns, [ /* hello namespace actions method */ ]), ...mapMutations((vm) => vm.$ns, [ /* hello namespace mutations method */ ]), },}
3.6 父子组件如何传递动态命名空间
我相信你,肯定发现了其中一个问题,this.$ns 只能 StoreMixin 的组件内获取到,那该组件的子组件怎么办呢?怎么解决子组件获取父组件的 namespace ?这个时候我们就需要借助 Vue 强悍的 mixin 的体系了,设计一个全局 mixin ,在组件创建的时候判断父组件有没有 $ns 对象,如果存在就将当前的组件的 $ns 设置为父组件一致,如果没有就跳过。
function injectNamespace(Vue) { Vue.mixin({ beforeCreate: function _injectNamespace() { const popts = this.$options.parent; if (popts && popts.$ns) { this.$ns = popts.$ns; const namespace = this.$ns; // 为组件扩展快捷方法和属性 this.$state = this.$store.state[namespace] this.$dispatch = (action, payload) => this.$store.dispatch(`${namespace}/${action}`, payload) this.$getter = //... this.$commit = //... } } });}// main.jsVue.use(injectNamespace);
这样子组件就会默认获取父组件设置的 namespace ,有了这个 mixin 的魔力,我们就可以把 mapXXX 方法的设计的扩展更优雅的一点,因为在 mapXX 方法中可以以 $ns 属性为默认的 namespace 。更清爽一点,保持和官方一致的风格, 这样才把 Vuex 更好的融入我们体系中去。
// code.vueexport default { computed: { ...mapGetter([ /* hello namespace store getter */ ]), ...mapState([ /* hello namespace state property */ ]), }, methods: { ...mapActions([ /* hello namespace actions method */ ]), ...mapMutations([ /* hello namespace mutations method */ ]), },}
3.7 最后一个完整的小栗子
通过下面的小栗子,我们可以看到对于开发者来说,只要按照标准的 Vuex 的开发方式来开发就可以了,好似什么都没有发生过 ^_^。其实在内部我们做了很多的努力,架构设计的目的就是【让简单的事情变得更加简单 , 让复杂的事情变得可能】。
store.js RSC 组件自有 store
export default { state() { return { mott: 'hello vue' } }, mutations: { changeMott(state) { state.mott = 'hello vuex' }, },}
text.vue text 子组件,mapState 自动动态获取命名空间
{{ mott }}
code.vue
到此,关于"Vuex状态管理的方法是什么"的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注网站,小编会继续努力为大家带来更多实用的文章!