当前位置:首页 > 技术文章 > 正文内容

同事用了个@vue:mounted,我去官网找了半天没找到

zonemu2个月前 (08-28)技术文章18

前言

大家好,我是奈德丽。

上周在做代码 review 的时候,看到同事小李写了这样一行代码:

<component :is="currentComponent" @vue:mounted="handleMounted" />

我第一反应是:"这什么语法?似曾相识的样子,有点像在 vue2中用过的 @hook:mounted , 但我们项目是 vue3,然后去 Vue3官方文档搜索 @vue:mounted ,结果什么都没找到,一开始我以为是他研究了源码,结果他说是百度到的,那我们一起来来研究研究这个东西吧。

从一个动态组件说起

小李的需求其实很简单:在子组件加载或更新或销 #技术分享毁后,需要获取组件的某些信息。这家伙是不是还看源码了,有这种骚操作,他的代码是这样的:

<template>

<div class="demo-container">

<h2>动态组件加载监控</h2>

<div class="status">当前组件状态:{{ componentStatus }}</div>

<div class="controls">

<button @click="loadComponent('ComponentA')">加载组件 A</button>

<button @click="loadComponent('ComponentB')">加载组件 B</button>

<button @click="unloadComponent">卸载组件</button>

</div>

<!-- 小李写的代码 -->

<component :is="currentComponent" v-if="currentComponent" @vue:mounted="handleMounted" @vue:updated="handleUpdated" @vue:beforeUnmount="handleBeforeUnmount" />

</div>

</template>

<script setup>

import { ref } from 'vue'

const currentComponent = ref(null) const componentStatus = ref('无组件')

const handleMounted = () => { componentStatus.value = ' 组件已挂载' console.log('组件挂载完成') }

const handleUpdated = () => { componentStatus.value = ' 组件已更新' console.log('组件更新完成') }

const handleBeforeUnmount = () => { componentStatus.value = ' 组件即将卸载' console.log('组件即将卸载') }

const loadComponent = (name) => { currentComponent.value = name }

const unloadComponent = () => { currentComponent.value = null componentStatus.value = '无组件' } </script>

我仔细分析了一下,在这个动态组件的场景下,@vue:mounted 确实有它的优势。最大的好处是 只需要在父组件一个地方处理 ,不用去修改每个可能被动态加载的子组件。想象一下,如果有十几个不同的组件都可能被动态加载,你得在每个组件里都加上 emit 事件,维护起来确实麻烦。

而用 @vue:mounted 的话,所有的生命周期监听逻辑都集中在父组件这一个地方,代码看起来更集中,也更好管理。

但是,我心里还是有疑虑:这个语法为什么在官方文档里找不到?

深入探索:未文档化的功能

经过一番搜索,我在 Vue 的 GitHub 讨论区找到了答案。原来这个功能确实存在,但 Vue 核心团队明确表示:

"这个功能不是为用户应用程序设计的,这就是为什么我们决定不文档化它。"

引用来源:github.com/orgs/vuejs/…

换句话说:

  • 这个功能确实存在且能用
  • 但官方不保证稳定性
  • 可能在未来版本中被移除
  • 不推荐在生产环境使用

我们来看一下 vue 迁移文档中关于 Vnode 的部分,关键点我用下划线标红了。有趣的是这个@vue:[生命周期]语法不仅可以用在组件上,也可以用在所有虚拟节点中。

虽然在 Vue 3迁移指南中有提到从 @hook: (Vue 2)改为 @vue: (Vue 3)的变化,但这更多是为了兼容性考虑,而不是鼓励使用。

为什么小李的代码"看起来"没问题?

回到小李的动态组件场景,@vue:mounted 确实解决了问题:

  1. 集中管理 - 所有生命周期监听逻辑都在父组件一个地方
  2. 动态性强 - 不需要知道具体加载哪个组件
  3. 代码简洁 - 不需要修改每个子组件
  4. 即用即走 - 临时监听,用完就完

但问题在于,这是一个 不稳定的 API ,随时可能被移除。

我给出的review意见

考虑到安全性和稳定性,还是以下方案靠谱

方案一:子组件主动汇报(推荐)

虽然需要修改子组件,但这是最可靠的方案:

<!-- ComponentA.vue -->

<template>

<div class="component-a">

<h3>我是组件 A</h3>

<button @click="counter++">点击次数: {{ counter }}</button>

</div>

</template>

<script setup>

import { ref, onMounted, onUpdated, onBeforeUnmount } from 'vue'

const emit = defineEmits(['lifecycle']) const counter = ref(0)

onMounted(() => { emit('lifecycle', { type: 'mounted', componentName: 'ComponentA' }) })

onUpdated(() => { emit('lifecycle', { type: 'updated', componentName: 'ComponentA' }) })

onBeforeUnmount(() => { emit('lifecycle', { type: 'beforeUnmount', componentName: 'ComponentA' }) }) </script>
<!-- ComponentB.vue -->

<template>

<div class="component-b">

<h3>我是组件 B</h3>

<input v-model="text" placeholder="输入文字">

<p>{{ text }}</p>

</div>

</template>

<script setup>

import { ref, onMounted, onUpdated, onBeforeUnmount } from 'vue'

const emit = defineEmits(['lifecycle']) const text = ref('')

onMounted(() => { emit('lifecycle', { type: 'mounted', componentName: 'ComponentB' }) })

onUpdated(() => { emit('lifecycle', { type: 'updated', componentName: 'ComponentB' }) })

onBeforeUnmount(() => { emit('lifecycle', { type: 'beforeUnmount', componentName: 'ComponentB' }) }) </script>

父组件使用:

<component
  :is="currentComponent"
  v-if="currentComponent"
  @lifecycle="handleLifecycle"
/>

<script setup>

const handleLifecycle = ({ type, componentName }) => { const statusMap = { mounted: ' 已挂载', updated: ' 已更新', beforeUnmount: ' 即将卸载' } componentStatus.value = `${componentName} ${statusMap[type]}` console.log(`${componentName} ${type}`) } </script>

优点 :稳定可靠,官方推荐

缺点 :需要修改每个子组件,有一定的重复代码

方案二:通过ref访问(适合特定场景)

如果你确实需要访问组件实例:

<component
  :is="currentComponent"
  v-if="currentComponent"
  ref="dynamicComponentRef"
/>

<script setup>

import { ref, watch, nextTick } from 'vue'

const dynamicComponentRef = ref(null)

// 监听组件变化 watch(currentComponent, async (newComponent) => { if (newComponent) { await nextTick() console.log('组件实例:', dynamicComponentRef.value) componentStatus.value = ' 组件已挂载' // 可以访问组件的方法和数据 if (dynamicComponentRef.value?.someMethod) { dynamicComponentRef.value.someMethod() } } }, { immediate: true }) </script>

优点 :可以直接访问组件实例和方法

缺点 :只能监听到挂载,无法监听更新和卸载

C 方案三:provide/inject(深层通信)

如果是复杂的嵌套场景,组件层级深的时候我们可以使用这个:

<!-- 父组件 -->

<script setup>

import { provide, ref } from 'vue'

const componentStatus = ref('无组件')

const lifecycleHandler = { onMounted: (name) => { componentStatus.value = ` ${name} 已挂载` console.log(`${name} 已挂载`) }, onUpdated: (name) => { componentStatus.value = ` ${name} 已更新` console.log(`${name} 已更新`) }, onBeforeUnmount: (name) => { componentStatus.value = ` ${name} 即将卸载` console.log(`${name} 即将卸载`) } }

provide('lifecycleHandler', lifecycleHandler) </script>

<template>

<div>

<div class="status">{{ componentStatus }}</div>

<component :is="currentComponent" v-if="currentComponent" />

</div>

</template>
<!-- 子组件 -->

<script setup>

import { inject, onMounted, onUpdated, onBeforeUnmount } from 'vue'

const lifecycleHandler = inject('lifecycleHandler', {}) const componentName = 'ComponentA' // 每个组件设置自己的名称

onMounted(() => { lifecycleHandler.onMounted?.(componentName) })

onUpdated(() => { lifecycleHandler.onUpdated?.(componentName) })

onBeforeUnmount(() => { lifecycleHandler.onBeforeUnmount?.(componentName) }) </script>

优点 :适合深层嵌套,可以跨多层传递

各种方案的对比

| 方案 | 实现难度 | 可靠性 | 维护性 | 集中管理 | 适用场景 | | ---

| emit 事件 | | | | | 大部分场景的首选 | | ref 访问 | | | | | 需要调用组件方法时 | | provide/inject | | | | | 深层嵌套组件通信 | | @vue:mounted | | | | | 自己项目可以玩玩,不推荐生产使用 |

总结

通过这次 code review,我们学到了:

  1. 技术选型要考虑长远 - 不是所有能用的功能都应该用,稳定性比便利性更重要
  2. 特定场景的权衡 - 在动态组件场景下, @vue:[生命周期] 确实有集中管理的优势,但要权衡风险
  3. 迁移策略很重要 - 不能一刀切,要有合理的过渡方案
  4. 代码review的价值 - 不仅仅是找bug,更是知识分享和技术决策的过程
  5. 文档化的重要性 - 未文档化的API往往意味着不稳定,使用时要谨慎

虽然 @vue:[生命周期] 在动态组件场景下确实好用,但从工程化角度考虑,还是建议逐步迁移到官方推荐的方案。毕竟,今天的便利可能是明天的技术债务。

当然,如果你正在维护老项目,且迁移成本较高,也可以考虑先保留现有代码,但一定要有明确的迁移计划和风险控制措施。

恩恩……懦夫的味道

相关文章

Ubuntu 24.10发行版登场:Linux 6.11内核、GNOME 47桌面环境

IT之家 10 月 11 日消息,Canonical 昨日发布新闻稿,正式推出代号为 Oracular Oriole 的 Ubuntu 24.10 发行版。新版在内核方面升级到最新 6.11 版本,并...

2023 年 10 个最佳 Linux 桌面发行版

Linux 操作系统在桌面领域的发展已经不再被忽视,越来越多的用户正在考虑切换到 Linux 上。在 2023 年,我们可以期待更多的 Linux 桌面发行版的推出和发展。这里列举了 10 个最佳的...

用IDEA开发如何用Git快速拉取指定分支代码?

1,准备空的文件夹,git init2,关联远程仓库,git remote add origin gitlab地址3,拉取远程分支代码,git pull origin 远程分支名再用IDEA打开项目即...

Jenkins 学习笔记(jenkins要学多久)

本学习笔记参考《Jenkins 2.x实践指南》。1. Jenkins 简介#Jenkins 是一款自动化的任务执行工具。通常用于持续集成/持续交付领域。可以通过界面或Jenkinsfile告诉Jen...

我常在使用的几个 VIM 插件(我常在使用的几个 vim 插件)

今天给你分享几个我觉得还不错的 VIM 插件,也许能给你带来一点「惊喜感」。vim主题插件 你完全可以让你的编辑器按照你喜欢的样子呈现,在 vimcolors 这个网站中,汇集了很多的主题,你可以进去...

02.Web大前端时代之:HTML5+CSS3入门系列~H5结构元素

Web大前端时代之:HTML5+CSS3入门系列:http://www.cnblogs.com/dunitian/p/5121725.html1.结构元素 可以理解为语义话标记,比如:以前这么写<...