前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >我攻克的技术难题 - BuildAdmin13:区区重新加载,vue居然用了mitt事件总线库

我攻克的技术难题 - BuildAdmin13:区区重新加载,vue居然用了mitt事件总线库

原创
作者头像
叫我阿柒啊
修改2024-02-13 07:52:23
1600
修改2024-02-13 07:52:23
举报

前言

关于弹出框,前几篇主要讲了如何渲染弹出框标签、实现弹出框的弹出位置、触发弹出框以及弹出框组件和tabs组件的数据交互。

接下来的几篇文章,将围绕着如何实现弹出框的五个标签。本篇文章讲的是第一个标签:重新加载

refresh

在上一篇中讲了tabs定义了onContextmenuItem方法,根据点击的标签name来实现对应的标签功能。重新加载对应的是refresh,我们看看是如何实现的。

代码语言:typescript
复制
case 'refresh':
    proxy.eventBus.emit('onTabViewRefresh', menu)
    break

可以看到就一行代码,这里的emit是什么呢?是上一篇讲的子组件调用父组件方法的那个emit吗?我们接着往下看。

mitt:事件总线

eventBus,事件总线。mitt是一个事件总线库,基于发布订阅事件在不同组件内进行通信。那么,如何使用mitt?为什么要使用mitt?

发布

mitt使用emit来发布事件。

代码语言:typescript
复制
this.$mitt.emit('eventName', args)

第一个参数是事件名称,第二个是参数。订阅方通过事件名称来接受到事件,然后就可以收到传过来的参数。

订阅

mitt通过on来订阅事件,然后定义一个方法,来接收处理事件传过来的参数。

代码语言:typescript
复制
this.$mitt.on('eventName', function(args))

BuildAdmin和mitt

从上面样例看到,调用emit和on需要使用this.$mitt,即mitt实例。在BuildAdmin中,我们使用的proxy.eventBus来调用的emit和on,也就是说proxy.eventBus代表的就是mitt实例,我们看看两者之间是如何关联的。

全局变量

在vue3中,config.globalProperties是一个全局配置选项,用于设置全局属性或方法,这些属性或方法会被注入到每个组件的实例中。

通常通过应用实例设置全局属性,main.ts中createApp创建的就是应用实例。

代码语言:typescript
复制
app.config.globalProperties.eventBus = mitt()

通过全局变量,将mitt实例绑定在了eventBus变量上,接下来就看如何获取这个变量。

获取变量

通过globalProperties设置的变量,在每个组件中都能访问到,所以我们就定义一个获取当前组件实例的方法。

代码语言:typescript
复制
import {ComponentInternalInstance, getCurrentInstance} from 'vue'

export default function useCurrentInstance() {
    if (!getCurrentInstance()) {
        throw new Error('useCurrentInstance() can only be used inside setup() or functional components!')
    }
    const {appContext} = getCurrentInstance() as ComponentInternalInstance
    const proxy = appContext.config.globalProperties
    return {proxy}
}

定义了useCurrentInstance方法。vue3中,getCurrentInstance就是获取当前组件实例的方法,这里将通过config.globalProperties获取到全局变量,然后赋值给proxy,这样通过proxy.eventBus就能获取到mitt实例了。

代码语言:typescript
复制
const {proxy} = useCurrentInstance()

然后在tabs中,就发布了一个名为onTabViewRefresh的事件,并传递了一个menu,即路由参数。然后就是接收这个事件重新加载页面,页面展示在layout布局中的main中,所以还要去main来了解重新加载的原理。

main

在第二篇的布局中,或者说在ElementPlus的布局组件中,main是路由中展示页面的地方,router-view根据router的跳转,来加载不同的页面。

从上图看,main就一个div来展示页面,当我们切换路由/tab时,当前组件默认被销毁,然后新建跳转的组件展示。

我修改了控制台页面的值,然后切换到其他tab再切换回来时,修改的值就没了。也就意味着,我之前的控制台的页面组件在切换时就被销毁了,在切换过来时又重新创建了一个组件。

如果切换tab就会删除我之前所有的修改,那tab栏的存在将毫无意义,这明显不是我们想要的结果,同时,我们根本也不需要重新加载的功能。

所以,我们需要一个缓存组件的功能,在切换tab时,页面不销毁重建,而是缓存起来直接引用即可。

keep-alive:组件缓存

在vue中,keep-alive功能是在多个组件间动态切换时,缓存原本要被移除的组件实例。在man中,添加keep-alive标签实现缓存。

代码语言:html
复制
<el-main class="layout-main">
    <div class="main-div">
        <router-view v-slot="{ Component }">
            <transition :name="config.layout.mainAnimation" mode="out-in">
                <keep-alive :include="state.keepAliveComponentNameList">
                    <component :is="Component" :key="state.componentKey"/>
                </keep-alive>
            </transition>
        </router-view>
    </div>
</el-main>

router-view中使用了v-slot,用来接收点击跳转路由是渲染在main区域的组件实例。然后这个组件实例,即Component就会被绑定在动态组件component上,然后被keep-alive缓存起来。

缓存实现

keep-alive中的 :include属性,指向的是组件缓存的一个集合,也就是state.keepAliveComponentNameList

addKeepAliveComponentName

接着就是向keepAliveComponentNameList中,放入需要缓存组件的名称。定义一个addKeepAliveComponentName方法。

代码语言:typescript
复制
const addKeepAliveComponentName = function (keepAliveName: string | undefined) {
    if (keepAliveName) {
        let exist = state.keepAliveComponentNameList.find((name: string) => {
            return name === keepAliveName
        })
        if (exist) return
        state.keepAliveComponentNameList.push(keepAliveName)
    }
}

很简单的逻辑,遍历keepAliveComponentNameList,组件存在就不放入,不存在就push。那什么时候调用这个方法来添加缓存呢。想想之前讲的tab切换是如何实现的,以及tab切换改变了什么?答案是watch路由

watch添加缓存

利用watch监控路由的变化,如果路由变化,就调用addKeepAliveComponentName将路由meta的keepalive放到keepAliveComponentNameList中。

代码语言:typescript
复制
watch(
    () => route.path,
    () => {
        state.componentKey = route.path
        if (typeof navTabs.state.activeRoute?.meta.keepalive == 'string') {
            addKeepAliveComponentName(navTabs.state.activeRoute?.meta.keepalive)
        }
    }
)

其中,keepalive是从后台请求的菜单中包含的字段,回填的就是组件名称。

还要一种需要添加组件缓存的情况,就是首次访问应用,第一个tab(控制台)的渲染,没有触发路由变化,也就不会触发添加缓存。所以需要在main首次初始化时,调用addKeepAliveComponentName将firstRoute对应的组件添加到缓存中。

代码语言:typescript
复制
onMounted(() => {
    nextTick(() => {
        if (typeof navTabs.state.activeRoute?.meta.keepalive == 'string') {
            addKeepAliveComponentName(navTabs.state.activeRoute?.meta.keepalive)
        }
    })
})

回想:在tabs中,将activeRoute=firstRoute,完成了初次赋值。然后才是在路由导航守卫赋值。

这个具体可以看之前讲的tabs的实现。

至此,就完成了组件缓存,在页面的修改也不会随着tab的切换而消失。

删除缓存

那么,重新加载就是从keepAliveComponentNameList中删除掉这个组件缓存,这时候就会触发这个组件的重新渲染,即组件的新建。

当tabs中通过mitt发布了onTabViewRefresh事件,在main中通过on接收到了事件,然后触发定义的回调函数。

代码语言:typescript
复制
onBeforeMount(() => {
    proxy.eventBus.on('onTabViewRefresh', (menu: RouteLocationNormalized) => {
        state.keepAliveComponentNameList = state.keepAliveComponentNameList.filter((name: string) => menu.meta.keepalive !== name)
        state.componentKey = ''
        nextTick(() => {
            state.componentKey = menu.path
            addKeepAliveComponentName(menu.meta.keepalive as string)
        })
    })
})

回调函数的逻辑就是:遍历keepAliveComponentNameList,最后通过filter过滤掉与onTabViewRefresh传入的menu相同的组件,这样就完成了页面的重新加载。

最后,再将加载后的新建的组件,通过addKeepAliveComponentName添加到缓存中,至此完美结束。

结语

重新加载的实现,学到了mitt、全局配置、keep-alive等知识点,是需要多个组件联动的一个功能实现,也是比较有意思的。下一篇将写关闭所有标签、关闭其他标签的功能实现。

我正在参与2024腾讯技术创作特训营第五期有奖征文,快来和我瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • refresh
  • mitt:事件总线
    • 发布
      • 订阅
      • BuildAdmin和mitt
        • 全局变量
          • 获取变量
          • main
            • keep-alive:组件缓存
            • 缓存实现
              • addKeepAliveComponentName
                • watch添加缓存
                  • 删除缓存
                  • 结语
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档