Vue组件通信:$refs与$parent的实战用法与避坑指南
发布时间:2026/10/6 9:50:27 锦皓数字建站

在组件通信这个老生常谈的话题里$refs和$parent可能是最容易被低估的两个角色。很多前端开发对 props、emit 用得滚瓜烂熟一到$refs和$parent就开始含糊什么时候该用、什么时候不该用、拿了组件实例之后能干嘛、为什么有时拿到的还是 undefined……这套东西用好了是真顺手用不好也真埋雷。这篇文章我就从实际场景出发把$refs和$parent的玩法、坑和背后那点设计逻辑一次性讲透。先说清楚定位。$refs和$parent都属于绕开常规数据流的通信方式props 是父组件主动把数据塞给子组件$emit是子组件主动通知父组件而$refs和$parent是让代码直接拿到对方组件实例想调方法就调方法想改数据就改数据。这种不走通道、直接上手的风格非常契合某些特定场景比如父组件需要主动触发子组件的表单校验、子组件要读取父组件的某个公共状态、或者在 v-for 列表中操作一组子组件实例。但它也有一个致命诱惑——用多了组件之间的边界会被慢慢腐蚀掉。这篇文章适合正在做 Vue 项目尤其是 Vue 2 或 Vue 3 Options API的开发者也适合想弄清楚组件实例到底是什么、vnode 挂着什么这类底层问题的朋友。React 或者 Flutter 的开发者也能从框架对照里获得一些启发——因为组件引用这个概念是跨框架存在的只是名字和写法不同。1. 组件通信全景$refs和$parent在坐标系里的位置要理解这两个 API 的价值先得把组件通信的全景图拉出来。我习惯把通信方式按数据和事件的流向分成三类自上而下父组件通过 props 把数据传给子组件子组件通过$emit把事件抛给父组件。这是官方主推的方式数据流清晰子组件不知道自己被谁使用复用性最高。旁路直连通过$refs获取子组件实例后直接操作或者通过$parent/$root直接访问父级链上的实例。这种方式的特征是绕过显式接口代码写起来痛快但组件之间的依赖关系隐藏在业务代码里别人看代码得顺着调用链去猜。全局共享EventBus、provide/inject、Vuex、Pinia 这类方案把数据放到组件树之外谁需要谁去取。解决的是跨层、跨分支、跨页面的数据共享问题代价是引入了全局状态管理和额外的概念复杂度。$refs和$parent就属于第二类——旁路直连。它们的核心手段都是拿到组件实例。那么问题来了组件实例究竟是什么在 Vue 3 里组件实例是一个带有$el、$data、$props、$refs、$emit等属性和方法的对象Vue 内部用proxy把实例属性做了响应式代理。我们调用this.$refs.child.someMethod()的时候本质上是拿到这个代理对象再调用它上面挂着的、由组件选项对象创建出来的方法。整个调用不走响应式依赖收集也不走事件总线就是最原始的函数调用只不过被 Vue 的代理层做了一层包装。这就解释了为什么很多人第一次用$refs会觉得这么简单——它确实简单因为这就是 JavaScript 里拿对象、调方法的常规操作Vue 只是提供了ref这个访问入口而已。1.1 为什么 props 和 emit 不能包打天下很多刚学 Vue 的同学会疑惑既然 props 和 emit 是官方主推为什么还需要$refs和$parent我的回答是接口通信解决的是协作问题直连操作解决的是控制问题。有些场景天然就是命令式的你用声明式的 props 去硬套会很别扭。举一个最典型的例子——自定义表单组件。假设你写了一个FormItem组件内部封装了 label 布局、校验规则、错误信息展示父组件想在点击提交按钮时让所有FormItem统一执行一次校验。如果用 props/emit 来做大概得这样给每个FormItem传一个validate-trigger的 boolean propFormItem 内部 watch 这个值的变化然后触发校验校验完了再 emit 一个validated事件回来。这会造成两个 pain point一是每次提交要两轮通信二是一个纯粹的控制指令被包装成数据流语义非常勉强。用$refs来做就干净得多// 父组件 const formRef ref(null) // Vue 3 Composition API function handleSubmit() { const valid formRef.value.validate() if (!valid) { message.error(表单校验未通过) return } // 提交逻辑 }注意看父组件不需要维护一个校验指令的状态只需要在需要的时候调用子组件的方法。这跟 DOM 操作里标签页控件调tabs.showTab(index)是一模一样的思维模型——命令式、直给、没有状态协调。这也是我把$refs归类为控制类通信的原因。$parent的场景则相反它解决的是子组件需要访问父级能力的问题。最常见的一个例子是子组件点击按钮后要从父组件的某个接口请求数据而父组件已经把这些逻辑封装成了方法。用 emit 的方式需要子组件 emit - 父组件监听 - 执行方法 - 传值给子组件链路长。用$parent子组件直接调this.$parent.fetchData()就完了。虽然这种写法耦合度高但如果你明确知道这个子组件只给某个父组件用代码反而更短意图也更直接。2.$refs深度拆解从获取 DOM 到操作组件实例$refs的基础用法大家应该都见过在模板里给元素或组件加一个ref属性然后通过this.$refs.xxx拿到对应的 DOM 节点或者组件实例。但很多人在什么时候能拿、拿了之后能干什么、拿不到怎么办这几个问题上吃过亏所以我把这几个点单独掰开来讲。2.1ref的三种常见形态ref在模板里可以挂在三种对象上每一种的表现都略有不同。第一种挂在原生 DOM 元素上。这时this.$refs.input拿到的是原生元素对象可以直接调用 DOM API。比如聚焦输入框input refinputRef typetext /this.$refs.inputRef.focus()这在用第三方库需要操作真实 DOM 时特别有用。比如你接入了某个日期选择器它内部不认 Vue 的 v-model你得在某个事件回调里拿到真正的 input 元素去设置值。第二种挂在子组件上。这是最核心的用法。this.$refs.child拿到的是子组件的实例对象可以访问它的 data、methods、computed也可以调用它内部的方法。这里有一个重要细节在 Vue 2 里拿到实例后你可以直接改this.$refs.child.someData xxx这个修改是响应式的Vue 3 里如果子组件用了script setup默认是关闭实例属性暴露的你得通过defineExpose显式把方法或状态暴露出来否则拿到的实例是阉割版的。这个变化很多人不知道在 Vue 3 项目里踩了为什么 $refs 上有 undefined的坑。第三种挂在 v-for 循环里的元素或组件上。这时候this.$refs.xxx拿到的不再是一个实例而是一个数组顺序和渲染顺序一致注意不是数据源顺序如果有条件渲染这个顺序只代表实际渲染的元素顺序。这个数组是动态的增删节点它也会跟着变化。利用这个特性你可以统一操作一组子组件。template ChildComponent v-foritem in itemList :keyitem.id refchildList :dataitem / /templatefunction validateAll() { const children this.$refs.childList || [] return children.every(child child.validate()) }需要注意在 Vue 3.5 之前v-for 里的ref在原生 DOM 节点上也是一个数组长度和渲染数量一致Vue 3.5 以后提供了useTemplateRef配合动态 key 之类的方式可以按需获取单个实例。不过我现在遇到的绝大多数项目还是走数组这条路这个在排坑部分我会再展开说。2.2 时机问题为什么 mounted 里有时还是拿不到$refs的填充时机是 Vue 渲染流程里一个特别容易踩的细节。先说结论created钩子里是绝对拿不到$refs的mounted里通常是能拿到的但如果你的 ref 所在节点被v-if按条件渲染mounted里也可能拿不到。原因不难理解。created阶段组件实例刚初始化模板还没有编译渲染DOM 树上当然不存在ref指向的节点或组件。等到mounted触发时组件已经完成了首次挂载正常情况下子组件实例应该已经注册到了$refs上。但v-if分支没渲染出来时对应的 ref 也不会存在所以在mounted里访问它只会得到undefined。这引出了两个非常实用的应对策略策略一用this.$nextTick把访问时机延后。mounted() { this.$nextTick(() { if (this.$refs.dynamicChild) { this.$refs.dynamicChild.init() } }) }策略二如果在条件渲染切换之后需要立即操作放在 watch 里配合 nextTick 更保险。watch: { showChild(newVal) { if (newVal) { this.$nextTick(() { this.$refs.dynamicChild this.$refs.dynamicChild.loadData() }) } } }这两种方式本质上都是在等 Vue 完成 DOM 更新和子组件实例挂载之后再去访问$refs只不过触发的入口不同。我在实际项目里几乎把 nextTick 当成了访问 refs 的前置动作这样踩坑概率能降到最低。2.3$refs的实战案例父组件主动控制子组件用$refs最常见的业务场景有两个表单校验聚合和子组件方法主动触发。先看表单聚合校验。现在大多数中后台项目用的是 Element Plus 或 Ant Design Vue它们封装好的 Form 组件内部也是通过 ref 来暴露校验方法的。我们自己封装业务组件时完全可以复刻这种思路子组件内部维护自己的校验逻辑父组件通过$refs拿到实例后循环调用校验方法最后汇总结果。// 父组件 function submit() { const forms [this.$refs.basicInfo, this.$refs.contactInfo, this.$refs.addressInfo] const results forms.map(form form.validate()) const isValid results.every(Boolean) if (isValid) { // 提交 } }这样做的好处是每个区块的校验规则内聚在各自的子组件里父组件不需要知道每个字段的校验规则只需要知道我需要你们各自校验自己。这是典型的命令式编排——父组件发号施令子组件自己执行。再看主动调方法。比如一个上传图片组件内部封装了压缩、预上传、进度展示等逻辑。点击某个外部按钮时希望它重新执行上传template div UploadImage refavatarUploader / button clickreUpload重新上传/button /div /template script export default { methods: { reUpload() { this.$refs.avatarUploader.uploadAgain() } } } /script这种写法比传一个 prop 控制重新上传要自然得多因为触发时机不固定而且子组件内部的uploadAgain是一次性的动作不是持续状态。命令式调用正好匹配一次性、事件驱动、由调用方决定时机的语义。3.$parent深度拆解拿父级实例这条路走得通但要想清楚$parent的光环比$refs小得多因为它天然带着反向依赖的烙印容易让组件失去独立性。但在合适的场景下$parent反而是最省事的解法。3.1 它到底指向什么$parent指向当前组件的父组件实例如果当前组件是应用根组件$parent是undefined。Vue 3 里还允许配置parent: true来让某个子组件不注册到父组件的$children中反过来从父组件视角看子组件的方式也在 Vue 3 被移除了$children官方建议通过$refs或 provide/inject 替代。$parent的使用非常简单在子组件里this.$parent.someData读取父组件的数据this.$parent.someMethod()调用父组件的方法。在 Options API 里这种调用还能双向联动——父组件实例的数据修改是响应式的子组件改了this.$parent.xxx父组件的视图同样会更新。这里有一个值得展开的点$parent拿到的是父组件的完整实例不是 props 也不是暴露接口所以子组件理论上可以修改父组件的任何 data、调用任何 methods。这种能力过强的设计就是为什么很多规范文档建议谨慎使用$parent的根源所在。3.2 跨层访问$parent和$root的区别有些同学会把$parent和$root搞混。$parent是直接父级$root是根实例。在多级嵌套里子组件想拿到最顶层的某个方法可以this.$parent.$parent.$parent.someMethod()但有经验的人不这么干——多层链式访问不仅脆弱中间某一层变了就断了还让人看代码时一头雾水。这种跨层需求更合理的方案是provide/inject或者状态管理。我见过一个非常生动的反面案例某个项目的菜单组件要从根实例拿用户信息代码写成this.$parent.$parent.$parent.$parent.userInfo。后来产品把菜单从二级结构调整成三级这段代码就炸了。这就是能用但没好下场的典型代表。3.3$parent的合规使用姿势既然$parent容易踩坑那什么场景是它的舒适区我的经验是当你明确知道子组件的唯一宿主是谁、子组件完全不打算复用、并且父组件需要被子组件反向调用的场景用$parent是可接受的。典型的是弹窗类组件。比如一个通用弹窗GlobalDialog它的打开逻辑由父组件控制但弹窗内部某个按钮点击后需要通知父组件刷新列表、关闭弹窗。如果你用 emit得在父组件里写一堆监听器和回调如果子组件知道父组件有refreshList方法直接调更直接// 子组件内 confirm() { // 自己的逻辑 this.$parent.refreshList() this.$parent.closeDialog() }当然更规范的做法仍然是 emit。我说可接受不代表推荐只是在代码量、可读性和明确性的权衡下这种写法在内部项目里是可以接受的。重点是你自己得知道这里耦合了父组件的接口不要美化成最佳实践。4. 实操示例父表单单区块校验 动态表单项收集为了让$refs和$parent的价值更具体我把它俩组合到一个真实业务场景里一个大型申请单页面拆成了基础信息、联系人、资质材料三个子组件提交按钮在父组件上。父组件在提交时要做三件事让每个子区块各自校验、收集所有区块的数据、最终统一提交。先看子组件的写法以基础信息为例!-- BaseInfo.vue -- template div classblock Form refformRef :modelformData FormItem label项目名称 propprojectName Input v-modelformData.projectName / /FormItem FormItem label预算 propbudget InputNumber v-modelformData.budget / /FormItem /Form /div /template script export default { name: BaseInfo, data() { return { formData: { projectName: , budget: 0 } } }, methods: { validate() { return this.$refs.formRef.validate() }, getData() { return { ...this.formData } } } } /script父组件把所有区块的 ref 放在一个对象里提交时统一调度!-- ApplyPage.vue -- template div BaseInfo refbaseInfo / ContactInfo refcontactInfo / QualificationInfo refqualificationInfo / button clicksubmit提交申请/button /div /template script export default { methods: { async submit() { const blocks [baseInfo, contactInfo, qualificationInfo] // 第一步逐个调用校验 for (const name of blocks) { const valid await this.$refs[name].validate() if (!valid) { this.$message.error(请完善表单内容) return } } // 第二步聚合数据 const payload blocks.reduce((acc, name) { return Object.assign(acc, this.$refs[name].getData()) }, {}) // 第三步提交 await submitApplication(payload) this.$message.success(提交成功) } } } /script这个例子展示了$refs的两个关键优势聚合控制和统一调度。父组件不需要知道每个子组件内部校验规则的细节只需要通过validate这个统一的方法名完成编排同时收集数据也是直接读取每个子组件暴露出来的数据快照而不是通过 emit 逐条传回。再看$parent在同一个场景里的应用方式。假设某个子组件内部有个补充说明折叠面板展开时如果发现父组件已经选择了某种申请类型需要联动填入默认说明文字。子组件可以直接读父组件的数据// QualificationInfo.vue 内部 expandPanel() { const parent this.$parent if (parent parent.applicationType enterprise) { this.formData.qualificationNote 企业资质说明待提交营业执照副本扫描件…… } }这段代码读取了父组件当前选中的申请类型避免了把applicationType通过 props 层层传下来。但要注意这里this.$parent指向的是直接父组件如果QualificationInfo外面还套了一层容器组件$parent指向就不对了。所以我一般会在子组件里用$parent之前先判断一下它是否真的具有目标方法或数据避免拿到的是中间层的无关实例。5. 方案选型参考什么时候别用$refs和$parent$refs和$parent用多了容易产生路径依赖什么问题都想去拿实例硬干。但作为负责任的从业者得给自己划一条边界——什么情况必须换方案。第一种情况数据需要跨多个层级共享。比如一个用户信息根组件从接口拿回来深层的孙子组件要用。你用$parent.$parent.$parent.userInfo去取是一次痛苦的链式访问你用 props 往下传中间层的组件全得声明转发也很烦。这种场景应该直接上provide/inject或者 Pinia。provide解决从上到下传递的问题Pinia 解决任意组件共享状态的问题都比$refs/$parent规范得多。第二种情况组件需要被多个不同场景复用。如果你的子组件既可能在 A 页面用也可能在 B 页面用而它在内部调用this.$parent.someMethod()那么 A/B 两个父组件都必须存在someMethod这等于给父组件强加了一个隐式接口复用性被削弱。遇到这种情况优先用 props emit 把依赖关系显式化让父组件自己决定怎么处理。第三种情况响应式数据流的驱动。$refs适合一次性调用但不适合持续的数据同步。如果你希望某个子组件的状态始终跟随父组件变化用$refs手动去 set 一遍是反模式——props 的响应式绑定才是正解。比如// 错误示范父组件为了同步一个状态到子组件每次变化都手动赋值 watch: { theme(val) { this.$refs.child.theme val } } // 正确示范直接通过 props 传下去 Child :themetheme /响应式系统的意义就在于让开发者少写这种手工同步代码。如果$refs被用来做持续状态同步说明组件设计大概率出了问题。我把常用的通信方案画成一张选型表放在下面方便快速决策通信场景推荐方案不推荐方案原因父组件给子组件传数据props$refs手动赋值props 具备响应式绑定代码更少子组件通知父组件触发事件$emit$parent调用方法emit 显式声明父组件可自由选择是否监听父组件主动调用子组件方法$refsprops 状态控制命令式场景用命令式方案语义更贴切跨多层组件共享数据provide / inject 或 Pinia$parent.$parent...链式访问脆弱且难维护多级嵌套但只要求直接父级协作$parent限定场景自定义 EventBus少量直连比全局总线更容易追踪这张表的核心逻辑只有一句话能走显式数据流的地方优先走显式数据流需要命令式控制的地方用$refs收敛跨层共享交给全局机制。不搞一刀切也不放任耦合。6. 常见问题排查与避坑实录写$refs和$parent的代码踩坑频率远高于 props/emit 这种稳扎稳打的方案。我把这些年真实遇到的高频问题整理成清单基本覆盖了绝大多数为什么我的$refs拿不到的求助。6.1this.$refs.xxx是 undefined这是最经典的报错场景。原因通常有五个访问时机太早在created里访问、mounted之前访问、或者父组件在子组件还没渲染完时就去访问。解决办法是用$nextTick或在mounted之后访问。ref 所在的节点没有渲染被v-if控制的分支条件为假时不会注册 ref。先确认条件已经变为真。ref 名字写错了这个看似低级但真的很多。模板里写的是refinputForm代码里写$refs.inputFormRef找半天找不到。检查模板和代码里的名字是否一致。子组件是异步组件如果子组件是用defineAsyncComponent或 Vue Router 懒加载创建的它的实例注册时机比同步组件更晚。需要在子组件mounted之后或者用v-if确保已经渲染完成。Vue 3script setup没有defineExposeOptions API 的实例属性默认全暴露但script setup里的变量默认不暴露$refs拿到的实例上大部分属性都是 undefined。需要显式用defineExpose把父组件可能需要访问的方法和数据列出来。6.2 v-for 里$refs拿到了数组怎么精确取到某个实例前面说了v-for中的 ref 是数组。如果你是固定列表按索引取就行this.$refs.itemList[index].someMethod()如果你在列表中间插入或删除元素索引会跟着变化这时候不要在模板里为 ref 加 index 后缀来区分——简单有效的方式是给每项绑定一个函数类型的 ref在函数里根据 item 的 id 存入一个自定义对象ChildComponent v-foritem in list :keyitem.id :ref(el) setChildRef(el, item.id) /setChildRef(el, id) { if (el) { this.childMap[id] el } else { delete this.childMap[id] } }这里函数 ref 的第二个触发条件是组件卸载时 Vue 会传入null所以需要做一次清理。这是 Vue 3 的函数式 ref 写法Vue 2 的ref也能用函数形式逻辑类似。为什么要用函数 ref因为 ref 数组的索引不可靠而 item.id 是业务上的稳定标识。用 id 做映射无论列表怎么增删都能稳定定位到目标组件。6.3$parent拿到了 null 或者父级链中断$parent为 null 的场景包括组件是根实例、组件在某个库封装里没有父级、组件的父级被v-if销毁等待时机。处理这种问题我在实际代码里习惯做防御性判断const parent this.$parent if (parent typeof parent.refreshList function) { parent.refreshList() }不要裸写this.$parent.refreshList()一旦父级不存在这一行直接抛 TypeError。防御性判断的代码多花两行但能避免整个组件崩溃。另一个头疼的场景是子组件挂在transition或keep-alive等内置组件里$parent指向的是这些特殊组件而不是业务父组件。遇到这种情况建议不要过度依赖$parent直接用 provide/inject 更可靠。6.4 用$refs修改子组件数据引发越权修改的隐性 bug$refs可以拿到实例后直接修改子组件 data这在短期的确好用但有个隐性风险父组件对子组件内部数据的修改子组件自身并不知情没有对应的 watch 或生命周期回调去处理副作用。比如父组件改了子组件的formData.budget子组件里如果有根据预算实时计算的总额只有在下次触发计算时才会刷新中间可能存在视图与实际值不一致的间隙。更稳妥的写法给子组件暴露一个方法把修改逻辑收进方法内部让子组件自己处理副作用。// 子组件 const setBudget (value) { formData.value.budget value total.value formData.value.projectCount * formData.value.budget } defineExpose({ setBudget }) // 父组件 this.$refs.child.setBudget(50000)这种暴露方法而不是暴露状态的做法本质上是在命令式入口和封装边界之间做了一个平衡。命令式的调用方式不变但修改逻辑仍然归属于子组件自己。6.5 Composition API 下两个 API 的新玩法现在大部分新项目用的是 Vue 3 Composition API。$refs和$parent在组合式写法里也都有对应替代或增强this.$refs在setup里不可用但可以通过 template 里的 ref 配合变量来获取child-component refchildRef /import { ref } from vue const childRef ref(null) // 模板里 refchildRef 会自动把实例赋给该变量在script setup中父组件如何访问子组件方法唯一方案就是defineExpose。子组件主动暴露什么父组件才能拿到什么。Vue 3.5 引入了useTemplateRef让模板引用的访问和类型推导更友好适合在复杂场景里减少魔法字符串。$parent在组合式 API 里用getCurrentInstance().parent可以获取但官方并不推荐随意使用因为 parent 的链式关系仍然和 Options API 一样依赖组件树结构。关于 Vue 3 的defineExpose我强烈建议所有封装过组件库、给其他团队提供通用组件的开发者养成一个习惯把需要对外暴露的接口当成组件的公开 API 来设计。defineExpose本质上是给你一个机会强制你思考我这个组件到底允许多少能力被外部直接触碰。这也是新版本框架给$refs带来的一个非常正向的约束——不是不能访问而是有纪律地访问。7. 跨框架对照refs不是 Vue 的专利看到热搜里同时出现 Flutter 组件通信和 Vue 的$refs我顺便把视野拉大一点。Ref 这种引用子组件实例然后直接调用的思路在主流前端/客户端框架里都有对应物ReactReact 16.3 以前的refs字符串、之后引入的createRef()和useRef()用法是先声明一个 ref 对象再挂到子组件上通过ref.current获取实例。React 的函数组件没有实例概念需要配合forwardRef和useImperativeHandle把子组件要暴露的方法透出来这和 Vue 的defineExpose在思路上是高度一致的。FlutterGlobalKey就是 Flutter 版的引用机制。给子组件例如一个 Form 组件赋一个GlobalKey通过globalKey.currentState获取组件的 State 对象再调用validate()之类的方法。Flutter 社区也在强调同样的纪律能用回调就把数据往上抛需要命令式控制时再通过 GlobalKey 调用。你看框架换了refs的边界问题依然存在。这其实说明了框架设计者的共识命令式调用是一个必要的逃生舱但不能变成主通道。各框架都在这个维度上加了约束——defineExpose、useImperativeHandle、GlobalKey的显式注册——目的不是阻止你用而是提醒你先想想是否真到了这个需求。8. 根据经验聊点实际的$refs和$parent的用前自问内容写到这儿架子上的原理、案例、排坑都有了。最后聊点我在实际项目中总结出来的判断标准。每次想用$refs或$parent之前先问自己三个问题第一个问题这个调用是一次性的行动还是一个持续的状态同步如果是前者——点按钮触发子组件的校验、删除后刷新列表、通知子组件加载一次数据——用$refs非常合适如果是后者比如要根据某个数据动态更新子组件的展示那就是 props 或 subscribe 的职责了。第二个问题调用之后子组件是否知道被谁调了用$refs从父组件调子组件方法主动权在父组件手里子组件内部不需要感知父组件的存在。用$parent从子组件调父组件方法子组件就必须假设自己的父级栈上存在某个方法。信息不对称是耦合的温床代码评审时我会特别关注那些$parent使用点因为它是隐式接口的重灾区。第三个问题这段逻辑如果写了注释注释会不会显得很尴尬这句话听起来有点玄学但确实有效。如果你发现自己要写的注释是这里直接操作子组件注意不要破坏子组件的状态那基本宣告你已经越过了合理的边界应该回到显式数据流。而如果注释是触发子组件的图片重新上传那这个$refs用法就是健康且有价值的。组件通信从来没有银弹$refs和$parent也不是什么深奥的黑魔法它们的存在是为了覆盖声明式通信鞭长莫及的角落。我的建议始终是常规数据流交给 props 和 emit命令式控制用$refs收敛跨层共享恢复用 provide / inject 和状态管理而$parent只在你完全控制组件树的前提下手起刀落。真搞明白了这些边界写出来的组件既好用又耐重构代码评审时也能用专业的方式解释清楚每个选择背后的权衡——而不是一句反正能用带过。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。