修身养性,知行合一

  • 首页
  • 爱码
    • 系统
    • 数据库
    • JavaScript
    • CSharp
    • Python
  • 生活
    • 文化
    • 美食
  • 杂谈
  • 关于
修身养性,知行合一
码字,杂谈
  1. 首页
  2. 爱码
  3. 前端
  4. JavaScript
  5. Vue
  6. 正文

如何写一个组件级别的全局状态管理

2021年12月30日 1196点热度 0人点赞 0条评论

起因

最近有人给我的 jz-gantt 提了个bug,说页面中放多个 gantt 组件会出现异常。我复现了一下,还真是。原因呢,也很简单,之前的升级小记中也记录过,就是因为 全局变量 冲突,当时没有意识到这会成为一个问题。这个之前还没考虑到,也是因为刚开始用 vue3,以为它会自动管理全局变量,但是,并不是~

因为挂载多个组件,导致变量名重叠,最后只有最后一个挂载的组件中的变量会生效,这就导致了多个组件出现了异常情况。简单来说,之前那种全局变量的方法是真正的全局变量,它适合用在项目中,而并不适合用在组件中。

修改思路

起初想直接挂个 vuex 完事,但总觉得很重,不友好,其实就是用一下状态管理,要组件级别的,挂个 vuex 完全没有必要,于是从其他方面入手考虑。

provide / inject

provide 与 inject 是 vue 的一大特性,也是官方推荐的一种 多层传递数据 的方式。

file

那在 vue3 中,可不可以让数据初始化之后,统一进行注入呢?答案是显然的。

有了这种思路,就可以着手开始改造了。

搭建过程

这里我只需要两个方法,一个负责 provide,另一个负责 inject 即可。

我们要知道,一定是在父组件中 provide,子组件才可以通过 inject 获取到对应的内容。不能在同一组件或同一层兄弟组件中使用这种方式。

那么我们需要在组件最外层套一个 Wrap 作为根组件,这样所有子组件就都可以获取到对应数据。

我们的组件结构可以如下:

Wrap.vue
 ├-- Child1.vue
 └-- Child2.vue
        ├-- GrandChild1.vue
        └-- GrandChild2.vue

首先在 Wrap.vue 中直接注入需要的数据,这里要做的仅仅是提供数据:

<script lang="ts">
import { defineComponent, ref, provide } from 'vue';

export default defineComponent({
  setup() {
    const count = ref(1);
    provide('count', count); // 将 count 注入到组件中
    return { count };
  }
});
</script>

这样就把变量注入到系统中,后代组件可以通过 inject 取出使用:

// Child1.vue
<script lang="ts">
import { defineComponent, inject, computed, Ref } from 'vue';

export default defineComponent({
  setup() {
    // 这里就可以获取数据,但是需要注意需要判空,所有 inject 返回的都是给定类型以及 undefined 两种类型
    // 除了判空,还可以给 inject 添加默认值
    const count = inject<Ref<number>>('count', 0);

    const myCount = computed(() => count.value * 2);
  }
});
</script>

这样就可以进行取值操作了。看上去很简单,但这里有两个问题:

  • 注入与取出的字符串应当被定义,而不是自行填写
  • 取值时的类型需要手动填写

如果小项目,这倒无所谓,但是随着项目增大,这样的问题就会尤为明显。而且这样的方式虽然简单,但是并不利于我们维护。所以我们要对它进行改造。

升级过程

为了以后可以更好的维护,我们可以参考 vuex 的写法,单独存放数据,再由各个组件统一调用。

首先新建一个文件,比如我们创建一个 src/store/index.ts,只需要填写两个方法:

import { ref, provide, inject, Ref } from 'vue';

// 统一管理变量名,可以使用字符串,也可以使用 Symbol
export const COUNT = Symbol('count');

export const initStore = () => {
    const count = ref<number>();
    provide(COUNT, count);
}

export const useStore = () => {
    // 这样会返回 number | undefined 类型
    count: inject<Ref<number>>(COUNT),

    // 如果不想判空,直接写类型,可以使用下面方式:
    count: inject(COUNT) as Ref<number>,

    // 再或者通过给默认值,有时候这样的写法更加安全可靠
    count: inject<Ref<number>>(COUNT, ref(0)),
}

然后,我们只需要在 Wrap.vue 中引用并初始化 initStore 即可:

// Wrap.vue
<script lang="ts">
import { defineComponent } from 'vue';
import { initStore } from '@/store';

export default defineComponent({
  setup() {
    initStore();
  }
});
</script>

而在其后代组件中直接调用 useStore 即可:

// SubChild2.vue

<template>
  <div>{{ myCount }}</div>
</template>

<script lang="ts">
import { defineComponent, computed } from 'vue';
import { useStore } from '@/store';

export default defineComponent({
  setup() {
    const store = useStore();

    const myCount = computed(() => store.count.value * 2);
  }
});
</script>

这样的写法与 vuex 基本无两,只不过这个更加轻量,非常适合组件内部使用,而且易于扩展,只需要两个方法即可。

如何测试它

很多教程到此就结束了。但我并不这么认为,测试仍然是重要的一部分,为了项目更加稳定,我们需要测试。但是针对上面的方法,我们如何测试呢?

起始很简单,只需要通过 @vue/test-utils 提供的 global 参数就可以快速构建:

import { shallowMount } from '@vue/test-utils';
import SubChild2 from '@/components/SubChild2.vue';
import { COUNT } from '@/store/index.ts';

const wrapper = shallowMount(SubChild2, {
  global: {
    provide: {
      COUNT: ref(1)
    }
  }
});

describe('Check SubChild', () => {
  it('SubChild html', done => {
    expect(wrapper.html()).toBe('<div>1</div>'); // true
    done();
  })
})

至此,才算完结。

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可
标签: JavaScript vue
最后更新:2021年12月30日

jeremyjone

这个人很懒,什么都没留下

打赏 点赞
< 上一篇
下一篇 >

文章评论

取消回复

文章目录
  • 起因
  • 修改思路
    • provide / inject
    • 搭建过程
    • 升级过程
  • 如何测试它
最新 热点 随机
最新 热点 随机
关于 *.vue 文件中使用 TypeScript 声明类型报错的解决方案 element table 加载时宽度闪烁问题 windows 无法登录便签、OneNote等应用 vue2 中 vuex 对 ts 的支持 封装一个极简的右键菜单 vue2 使用 @vue/composition-api 的一些问题
JavaScript 之 canvas(四)-- 绘制文字 Windows 10上获得类似Mac的流畅字体 Qt lnk1158 无法运行rc.exe 的解决方案 Windows Server 2019安装与配置(三) 封装一个极简的右键菜单 真丶深入理解JavaScript异步编程(一):异步

(っ•̀ω•́)っ✎⁾⁾ 开心每一天

COPYRIGHT © 2021 jeremyjone.com. ALL RIGHTS RESERVED.

THEME KRATOS MADE BY VTROIS

京ICP备19012859号-1

京公网安备 11010802028585号