修身养性,知行合一

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

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

2021年12月30日 3020点热度 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
    • 搭建过程
    • 升级过程
  • 如何测试它
最新 热点 随机
最新 热点 随机
推一个vscode纯黑主题 vue 的递归插槽穿透 Github Pages SPA 重定向 行间距引出的 DOCTYPE 怪异行为 写个小彩蛋 绘制一个可重用的线条阴影
vue 的递归插槽穿透推一个vscode纯黑主题
TypeScript 类型找不到 使用 windows 命令启动某个程序 windows下自动备份文件 浅谈if...else与switch的区别和效率问题 绘制一个可重用的线条阴影 若您正以管理员身份运行 Visual Studio Code - Insiders 用户范围的安装,更新功能会被禁用。

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

COPYRIGHT © 2021 jeremyjone.com. ALL RIGHTS RESERVED.

THEME KRATOS MADE BY VTROIS

京ICP备19012859号-1

京公网安备 11010802028585号