资讯动态

Vue 3 父子组件通信详解:props、emit、v-model 及实战避坑

发布时间:2026/10/18 7:10:25 来源:尧图企业网站定制
我们团队最近接了一个中台项目重构的活代码是老同事留下的业务逻辑不算复杂但父子组件之间的通信写得相当随意。有的页面里 props 直接塞了一个对象进来子组件内部改得飞起一个字段出错排查的时候要从页面入口一路追到三层组件下去有的组件靠$parent链往上摸事件看着能用换个人接手直接懵。前前后后捋了两周把这一坨理清楚之后我最大的感受是Vue 的父子组件通信本质上就那么几种套路但真正能在一个项目里保持一致、可维护、不埋雷的写法比想象中要少。这篇文章我就结合这次重构里的真实案例把父子组件的核心机制、常用通信手段、以及我在实际项目中踩过的坑一次讲清楚。内容按 Vue 3 script setup为主部分地方会带一下 Vue 2 的对比方便正在做老项目迁移的同学对照。无论你是刚接触组件化开发的新手还是已经写了一段时间但总觉得能用但说不清为什么的进阶开发者这篇都值得你花十几分钟读完。1. 先搞清楚父子组件到底在传递什么数据流与控制权的边界很多教程一上来就讲 props 和 emit 的语法但我觉得先理解父子组件之间到底传递的是什么更重要。组件化开发的本质是职责拆分而职责拆分的核心是数据流和控制权的明确划分。1.1 props 不只是传值而是父组件对子组件的单向授权所谓父传子形式上是通过 props 把数据交给子组件但它的语义远不止把值传进去这么简单。更准确的理解是父组件向子组件声明了一组它可读的输入项子组件只能读取和使用这些输入没有权利去修改它们。这里有个非常容易被忽略的点——props 里传的如果是引用类型对象、数组子组件读到的其实是同一个内存地址。也就是说子组件里如果直接改props.someObj.xxx newValue它改的并不是 props 的某个字段而是父组件里那个对象本身。这就会造成子组件悄悄改了父组件的状态但父组件毫无知觉的情况。我见过一个很典型的案例。某项目的一张列表页父组件管理搜索条件对象把它传给子组件做表单展示。子组件在内部对某个字段做了默认值填充props: { searchParams: { type: Object, required: true } }然后在子组件的某个方法里想当然地写了if (!this.searchParams.status) { this.searchParams.status ALL }这段代码运行后父组件的搜索条件对象就被子组件悄悄改了。表面上页面功能正常但实际上数据流的单向性已经被破坏。如果此时父组件还有对searchParams的监听或计算逻辑很容易出现没动父组件代码但行为变了的诡异问题。正确的做法是子组件如果需要基于 props 做派生或补充应该先把值拷贝一份到自己内部的数据里。const localParams ref({ ...props.searchParams })或者用计算属性做单向派生const displayParams computed(() ({ ...props.searchParams, status: props.searchParams.status || ALL }))这样既保留了子组件的灵活性又不污染父组件的状态。1.2 单向数据流不是为了限制你而是为了让状态变化可追踪Vue 官方文档里反复强调单向数据流很多人觉得这是规矩太多但实际上它是大型项目里状态可控性的基石。如果任何组件都能随意修改共享状态那当状态出问题的时候排查范围会从这一条链路上谁改过它扩大到所有接触过它的组件。我做过的几个长期维护的项目里凡是早期没遵守单向数据流的后期 bug 修复成本都明显偏高。因为你能看到 props 传进来的值是什么但你不知道在哪个犄角旮旯里被谁改了。单向数据流的意义在于数据的每一次变化都有一个明确的来源父组件通过 props 传下去子组件通过 emit 提上来变化路径清晰调试时顺着链路找就行。所以在设计组件的时候我一般会先画一条数据链数据从哪来、在哪些层被消费、哪些层需要把它改掉、改完如何回流。这条链越短组件越容易维护。如果一个组件层级很深中间层纯粹是过手数据那就该考虑是不是要扁平化数据结构或者用组合式 API 把这个数据链提取到组件外部管理。2. props 的完整使用姿势从定义到消费再到防坑props 是父子组件通信最基础、最高频的手段但细节特别多。语法大家都背过可真正落到项目里正确姿势和反模式之间的差别往往就是 debug 时间和代码可读性的差别。2.1 声明 props 的几种方式与适用场景Vue 3 中声明 props 有两种主流形式字符串数组和对象形式。对象形式可以声明类型、默认值、必填项和校验函数。字符串数组写法defineProps([name, age, info])这种写法适合临时 demo 或业务原型阶段完全不做校验数据不对了只能靠运行时表现去猜。一旦组件开始被多个页面复用我建议立刻升级成对象形式script setup const props defineProps({ name: { type: String, default: }, age: { type: Number, default: 0, validator: (value) value 0 value 150 }, info: { type: Object, default: () ({}) } }) /script这里有一个必须注意的细节default如果是对象或数组必须写成函数返回新实例的形式不能直接写{}或[]。这是因为如果直接写引用值所有没传该 prop 的组件实例会共享同一个对象一个组件改了它其他组件全跟着变。属于看起来没毛病但实际埋雷的典型。类型那里除了基础类型还可以写构造函数甚至可以一次性声明多个可能的类型width: { type: [Number, String], default: 100 }这在设计通用型组件时非常有用。比如某个按钮组件size参数既支持传数字像素值又支持传small | medium | large这种预设字符串就可以用联合类型声明再在内部做归一化处理。2.2 子组件里消费 props 的细节解构、响应性、命名习惯在script setup中对 props 做 ES 解构有个陷阱解构出来的变量会丢失响应性。script setup const { name, age } defineProps([name, age]) // 下面的代码不会响应式更新 console.log(name) /script如果模板里直接用解构出的变量首次渲染没什么问题但后续父组件更新 props 时模板不会拿到新值。正确用法是直接通过props.xxx访问或者用toRefs/toRef转成 ref 再解构script setup const props defineProps([name, age]) const { name, age } toRefs(props) // 此时 name 和 age 都是 ref具备响应性 console.log(name.value) /script命名习惯方面我踩过一个特别典型的坑。有一次我定义了一个 prop 叫defaultValue然后在子组件里又想定义一个内部变量也叫这个名字。原本以为名字冲突顶多报个警告结果 Vue 在运行时直接把内部的给顶掉了页面渲染出来的数据怎么都不对。排查了半天才意识到是命名冲突。避免这类问题很简单——props 命名时要有识别度内部变量也不要用props、attrs这种关键字。推荐小驼峰命名 props模板中传参时用 kebab-case 写法child-component default-value1 update:valuehandleChange /2.3 不该通过 props 传的东西函数与复杂逻辑props 里能不能传函数能。但我不建议把函数作为 props 传进去原因有二一是函数作为 props 会让子组件的接口变得很模糊调用方需要知道这个函数有多少参数、返回值是什么、异常怎么处理耦合度比事件高得多二是函数经过 props 传递时Vue 不会对它的响应性做任何包装父组件换了函数引用子组件里的调用位置如果不做处理拿到的还是旧函数。在 Vue 中父子组件间触发父组件逻辑的正统方式是emit自定义事件。它把子组件决定什么时候通知父组件这件事表达得很清楚比传函数再等回调要直白。传函数的方式更接近 React 的思维习惯在 Vue 生态里属于能用但不地道。如果真的需要一个类似回调的机制我推荐的做法是把回调逻辑定义在父组件里通过事件参数传给子组件调用!-- 父组件 -- ChildComponent submit(payload) handleSubmit(payload, extraData) /这样职责边界清晰子组件只负责在合适的时机触发submit事件父组件负责决定如何处理数据。子组件完全不需要知道父组件那边的业务逻辑。3. emit 的深层逻辑事件不是简单的子调父而是一种API设计自定义事件是子组件向父组件通信的核心手段。但很多人用 emit 只是把父组件的方法调了一下完全没有把它当成组件对外 API 的一部分来设计导致组件边界越来越模糊。3.1 为什么事件是子组件回传数据的首选方式站在父组件的视角它把数据交给子组件然后把控制权也交了出去。子组件什么时候需要更新数据、数据变成什么样这些只有子组件自己知道。事件机制的本质是让子组件在它认为合适的时机通知父组件数据该变了。事件和函数 props 最本质的区别在于事件传递的是发生了什么函数传递的是去做什么。前者是声明式的后者是命令式的。声明式的好处在于子组件不需要知道父组件收到事件之后会干什么它只负责表达意图。这使得子组件可以在完全不感知父组件业务的情况下独立复用。举个例子一个标签选择器组件内部维护选中状态。点击标签时它并不直接调用某个外部方法去跳页面或存数据而是触发update:selected事件并把新的选中值作为参数交出去。父组件收到事件后想干嘛都行可以存到表单里、可以触发接口请求、甚至可以完全不处理。子组件完全不用关心。这就是我认为事件通信的核心价值——它让组件之间的耦合降到最低只通过约定的协议进行交流。3.2 v-model 的本质modelValue与update:modelValue的语法糖Vue 3 中v-model指令的本质是一个组合语法糖它同时做了两件事——绑定modelValueprop 和监听update:modelValue事件。!-- 使用 v-model -- ChildComponent v-modelpageTitle / !-- 等价写法 -- ChildComponent :modelValuepageTitle update:modelValuepageTitle $event /自定义组件如果想支持v-model只需要接收一个modelValueprop并在需要更新值时触发update:modelValue事件即可script setup const props defineProps({ modelValue: { type: String, default: } }) const emit defineEmits([update:modelValue]) function handleInput(event) { emit(update:modelValue, event.target.value) } /script template input :valuemodelValue inputhandleInput / /templateVue 3.4 之后新增了defineModel这个宏可以进一步简化 v-model 双向绑定的组件实现script setup const model defineModel({ type: String, default: }) /script template input v-modelmodel / /templatedefineModel会自动处理modelValue和update:modelValue的声明与绑定模板里v-modelmodel就能直接读写。这个 API 对封装表单类组件来说非常省事但有个注意点defineModel编译后的行为依然依赖 props 和 emit它只是语法糖。3.3 多 v-model 绑定与事件命名规范一个组件只支持一个v-model往往不够用。比如一个日期范围选择器需要同时绑定开始日期和结束日期。Vue 3 从 3.0 开始就支持多个 v-model 绑定核心就是给事件加上自定义参数名DateRangePicker v-model:startstartDate v-model:endendDate /对应的实现里需要定义start和end这两个 prop并触发update:start和update:end事件。Vue 3 的v-model参数化机制让这种多绑定的写法非常自然甚至可以用来替代一部分一个组件多个 props 需要联动更新的场景。事件命名上我坚持一个原则全部小写字母加短横线kebab-case。比如update:modelValue我不会在模板里写成update:modelValue之外的任何变体。自定义业务事件像submit-form、close-modal、select-changed也统一用 kebab-case因为 HTML 标签和属性天生不区分大小写写在模板里大小写混用容易出问题。在 JS 代码里声明事件名时保持与模板完全一致避免声明时用驼峰、监听时用短横线导致监听不到。3.4 事件中传参的组织方式一参数还是对象参数事件回传的数据怎么组织也算一个看起来无所谓、实际影响后续维护的决策。我见过两种风格单个参数emit(change, pageNo)多个参数emit(change, pageNo, pageSize, total)单个参数的好处是语义聚焦坏处是如果后续要加参数调用方代码全要改。多个参数省去了包装对象的重复代码但参数一多父组件处理的时候很容易搞混。我的经验是超过两个参数就坚决用对象。对象参数还有一个额外好处——接收方可以只解构自己关心的字段对新增字段天然免疫const onChange ({ pageNo, total }) { // 只需要 pageNo 和 total不用关心未来新增的字段 }这样未来子组件要补充排序信息只要在对象里加一个字段父组件不需要任何改动。而如果当初用的是位置参数加一个参数就意味着所有调用处都要核对一遍。4. 除了 props 和 emit那些高级但常用的通信姿势当组件之间的数据流变得复杂或者层级很深仅靠 props 和 emit 会写出非常冗长的中间传递代码。有些场景下合理地使用其他通信手段会让代码结构明显改善。下面几个是我在实战中用得比较多的。4.1 defineExpose ref父组件直接调用子组件方法的使用边界defineExpose是script setup语法中让父组件访问子组件内部状态和方法的手段。配合模板 ref 使用父组件可以直接调用子组件暴露的方法。!-- 子组件 Child.vue -- script setup import { ref } from vue const innerCount ref(0) function reset() { innerCount.value 0 } defineExpose({ innerCount, reset }) /script!-- 父组件 -- template Child refchildRef / button clickcallChildReset重置子组件/button /template script setup import { ref } from vue import Child from ./Child.vue const childRef ref(null) function callChildReset() { childRef.value?.reset() } /script这个手段适合什么场景适合子组件内部有一个比较完整、自洽的行为父组件只想在特定触发条件下让它执行而不是去管它执行的具体细节。比如一个表格组件它自己管理排序、分页、筛选父组件的某个按钮要让它回到初始状态直接调tableRef.reset()比对表格组件逐项传 props 去重置要优雅得多。但这里有个边界要拎清defineExpose用得过多会让组件之间的耦合从数据层上升到方法层父组件开始依赖子组件的内部实现。如果一个子组件暴露了七八个方法你可要警惕了——这通常意味着子组件的 API 设计得不够好很多逻辑应该通过事件或 props 来解决而不是开放内部方法。我在实践中给自己定的标准是暴露方法一般不超过两三个且每个方法都是原子的、语义完整的动作比如 reset、focus、validate。拆得过碎的暴露基本等于把子组件内部细节泄露了出去。4.2 插槽与作用域插槽让父组件接管子组件的渲染细节插槽是父子组件通信里容易被低估的手段。props 传的是数据插槽传的是模板片段。当子组件不确定某个区域怎么渲染时插槽就把决定权交回给父组件。基础插槽!-- 子组件 Card.vue -- template div classcard header slot nametitle默认标题/slot /header main slot / /main /div /template父组件使用Card template #title自定义标题/template template #default卡片主体内容/template /Card作用域插槽更强大它允许子组件在插槽内容中向父组件暴露内部数据!-- 子组件 Table.vue -- template table tr v-forrow in rows :keyrow.id slot namerow :rowrow :indexindex / /tr /table /template!-- 父组件使用 -- Table :rowsrows template #row{ row, index } td{{ index 1 }}. {{ row.name }}/td /template /Table作用域插槽在封装通用组件时特别管用。很多半通用组件比如数据表格、下拉选择器、弹窗它们的骨架和交互逻辑可以复用但每一处的单元格渲染、选项内容、弹窗底部按钮都不一样。如果全部用 props 去描述这些渲染差异props 会膨胀到没法维护。用作用域插槽组件只负责提供数据上下文父组件负责具体长什么样。4.3 provide / inject跨层级通信的一次性便捷方案如果组件嵌套很深从顶层往底层逐层传 props中间层每一层都要写一次传递代码非常啰嗦。provide和inject就是为了解决这种深层次但不需要每一层都参与的场景。!-- 顶层组件 -- script setup import { provide, ref } from vue const theme ref(dark) provide(theme, theme) /script!-- 底层组件 -- script setup import { inject } from vue const theme inject(theme, light) // 第二个参数是默认值 /scriptprovide 配合 ref 或 reactive 提供数据时inject 方获得的是响应式引用双方数据天然同步。这比父传子、子传父的逐层传递省了好几道模板代码。不过我不建议项目里在整个业务链路上滥用 provide/inject。原因在于它破坏了组件树的显式数据流——你看到一个组件从inject拿数据很难直接判断数据来自哪个层、谁在维护它。这相当于一个隐式的全局变量用得越多组件的可读性和可维护性就越差。我给出的使用建议是适合跨多层的全局配置类数据主题、语言包、用户信息、当前租户等适合跨层调用某个稳定的能力消息提示方法、路由跳转方法、权限判断方法不适合逐层业务数据的传递尤其是那种两个层之间有关系但中间层隔了好几层的业务数据如果发现自己用 inject 注入的数据需要被新注入方修改、联动那大概率设计有问题应该把这块状态提取到一个独立的组合式函数或全局状态管理中而不是继续藏在注入链路里。5. 我在真实项目中踩过的坑props 直接改、emit 命名不一致与 v-model 的失效这一节是整篇文章里我最想强调的部分因为很多问题光看文档是看不出来的只有真正在项目里被坑过才会长记性。5.1 props 引用类型被改后父组件却毫无知觉前文提到过props 的引用类型是同一个内存地址。有一种很隐蔽的改法是这样的子组件里对 props 传进来的数组直接调用push或splice方法。props.list.push(newItem)这行代码没有触发任何 Vue 的警告甚至子组件的视图可能也会跟着更新。但从数据流角度看它已经破坏了单向数据流而且问题的隐蔽性极高——父组件里可能有个watch在监听list但如果你监听的是引用本身的变化比如直接watch(list, ...)由于数组没有换引用watch 不会触发。我最后排查这类问题的经验是在父子组件之间传递引用类型数据时子组件内部永远不要直接调用那些会原地修改对象或数组的方法。需要改就基于传入的 props 做一个新副本通过事件把新副本往上传。这个原则我在代码评审时几乎是必提的一条。一开始有人觉得这样多此一举但经历过一次两个组件视图不同步但共享同一数组的血泪教训之后所有人都老实了。5.2 事件名大小写命名不一致导致监听失效Vue 3 的事件系统对大小写不敏感但会产生一个很隐蔽的行为差异。有一次我写了一个子组件内部用驼峰声明了一个事件模板里用 kebab-case 监听结果发现功能时好时坏。后来查了文档才发现Vue 的模板编译器会先把事件名转成 kebab-case 再做匹配。举一个具体的例子!-- 子组件 -- script setup const emit defineEmits([selectChanged]) /script!-- 父组件 -- ChildComponent select-changedhandleChange /在 Vue 3 中selectChanged和select-changed会被识别为同一个事件表面上功能正常。但如果你用defineEmits声明了selectChanged某个地方又写了selectChanged编译器可能不会像 kebab-case 那样做兼容转换导致监听失效。最稳的方案是声明事件、触发事件、监听事件全部统一使用 kebab-case。宁可让代码多写一个短横线也不要让大小写问题成为 bug 源头。5.3 v-model 绑定了不可变引用更新永远不生效还有一个经典问题用v-model绑定了某个父组件内部计算出的属性或者绑定了props本身。比如父组件里有这样一个计算属性const fullName computed(() ${first.value} ${last.value})然后模板里写ChildComponent v-modelfullName /由于fullName是一个只读的计算属性子组件触发update:modelValue时父组件尝试给fullName赋值就会直接报错Write operation failed: computed value is readonly。但如果父组件里写的是v-modelprops.xxx情况更隐蔽。props 在父组件内部也是只读的这个赋值同样会失败。问题在于报错不一定立刻出现尤其是在生产环境Vue 可能只打一条警告然后视图就卡在旧数据上不动了。排查这种问题很花时间。结论很简单v-model必须绑定一个可写的响应式数据最常见的是ref或reactive的某个属性或组件内部定义的变量。任何看起来像变量但实际上由其他地方派生出来的值都不适合直接拿来做 v-model。5.4 子组件在 watch 中同步修改 props 引用导致循环更新有一种写法很诱人子组件 watch 某个传进来的 props然后对 props 引用做原地修改期望让父组件联动看到新值。watch(() props.status, (newVal) { // 想通过这种方式让父组件的 status 自动变为 processed props.status processed })这代码一写Vue 会立即警告Set operation on key status failed: target is readonly。即便你关掉了警告这个逻辑也不会正常工作因为 props 的变更不会自动回流到父组件。正确的模式应该是watch props 变化后在子组件内部维护一份副本再通过事件把结果传回父组件让父组件自己决定要不要更新原始数据。这个模式看起来繁琐但它是 Vue 数据流的本意——谁的数据谁决定怎么改。6. 组件 API 设计的实际方法与选型清单前面讲的都是具体通信手段的原理与坑最后这部分聊聊方法论面对一个实际的业务场景怎么判断该用哪种方式怎么度量组件 API 写得好不好。6.1 从数据从哪里来、要到哪里去出发做选型我设计组件时第一步不是写代码而是把数据流画出来。现在描述给团队新人的时候我会用几个标准问题来引导这个数据是子组件自己维护还是父组件传入后在本地派生用户交互导致数据变化时变化应该由谁拥有、谁声明中间隔了多少层逐层传 props 是否值得父组件是否真的需要知道子组件的内部状态变化这几个问题的答案基本能导向最终方案。如果数据只在子组件内部使用父组件只需拿到最终结果那 props 进、emit 出就足够了。如果父组件还需要动态控制子组件的某一块渲染结构那就让子组件把这部分作为插槽暴露出去。下面是我在项目里内部整理的一个选型参考表实际开发时我经常拿出来对照通信场景推荐方案不推荐方案简单数据下行层级浅propsprovide / inject简单数据上行触发父组件行为emit 事件函数作为 props表单类双向绑定v-model多参数绑定手动 props 响应 事件父组件需要直接控制子组件行为ref defineExpose深层依赖子组件内部方法跨多层传递配置类数据provide / inject逐层 props不同区域渲染结构各不相同插槽 / 作用域插槽大量 props 描述渲染差异全局共享业务状态状态管理库如 Pinia到处 provide / inject表格是我给团队做代码评审的辅助工具不是说上面的不推荐一定不能用而是说在多数情况下推荐项会是更符合 Vue 设计哲学的选择。6.2 组件 API 质量的三个判断标准单一职责、最小暴露、消息语义化把父子组件通信的所有手段都用熟之后真正拉开差距的其实是组件 API 的设计水平。我总结出三个标准在每次写组件之前都会过一遍第一个是单一职责。一个组件该不该接收某个 props判断标准很简单——去掉这个 props组件是否仍然成立如果组件为了支撑几十种不同场景把 props 堆到二十多个每个 props 还有一堆组合关系那这个组件的职责一定已经不单一了。更好的做法是把组件拆小用组合的方式去应对差异而不是让一个组件承载一切。第二个是最小暴露。能通过 props 和事件解决的不用 refdefineExpose能通过插槽解决的不要暴露一堆方法。组件对外暴露的接口面越小调用方需要理解的东西就越少组件自身的可维护性也就越高。我在评审时经常把组件 props、事件和暴露方法的数量统计出来如果总数超过 15 个基本都会建议重审组件拆分的合理性。第三个是消息语义化。事件名和 props 名要像一个完善的 API能够准确表达含义。我遇到过emit(aaa)触发后父组件愣了半天不知道这个 aaa 意味着什么的情况。好的事件名应该是一个动宾短语比如update:status、select-change、submit-success。好的 props 名应该直接描述数据的含义而不是实现方式比如options而不是optionListData、loading而不是isLoadingVisible。6.3 一次完整的设计评审文件上传组件的通信方案拿我们项目中一个图片上传组件的评审过程来举例。组件需求是用户选择图片后组件内部负责压缩、预览、上传上传完成后父组件要拿最终图片地址还要能对删除已上传图片做记录。最初的方案是父组件把上传 URL 传进去组件内部把上传和删除逻辑都包掉对外只抛update:fileList事件。这个方案看起来简单但细想你会发现文件上传有很多中间状态——上传进度、压缩失败、上传失败——这些父组件完全不知道。用户看到失败的提示却无法在父层面处理只能靠组件内部自己弹提示。评审后的最终方案是父组件负责提供上传配置URL、请求头、鉴权信息通过 props 传入子组件内部维护上传过程的状态进度、错误、预览图对外只暴露一个带完整结构的事件emit(upload-complete, { file, url, progress })如果父组件需要完全自定义每张图片的展示样式子组件开放一个作用域插槽#item{ file, status, progress }删除操作通过update:fileList事件回传新的文件列表这个方案里子组件保留了上传行为的控制权父组件不需要了解上传细节但通过事件和插槽又能把关键信息拿回来做个性化处理。props 只有三个上传配置、文件列表、最大数量事件有两个插槽一个。组件 API 的简洁度和扩展性都达到了比较理想的状态。这个例子说明一个好的通信设计不是把所有高级 API 都用上而是让每个 API 出现在它最该出现的位置不多不少。7. 最后再分享几个可以让代码立竿见影的小习惯聊了这么多机制和坑最后分享几个我写父子组件时的固定习惯每个都很小但组合起来效果明显。第一每次写子组件之前先写好defineProps和defineEmits哪怕只是一个空声明。这不仅是让编辑器有类型提示更重要的是逼自己先想清楚组件对外暴露什么、可以通知外部什么。这个先定契约再写实现的习惯能在源头上避免一坨代码写完之后发现 props 设计混乱的问题。第二子组件里永远不要改 props 引用类型字段。不是谨慎地改是坚决不改。派生数据用 computed 或内部 ref watch 处理回传数据一律走事件。这是这几年维护大项目总结的最硬一条经验。第三所有事件名统一 kebab-case所有声明的事件在派发和监听处保持完全一致。这个习惯能直接消灭一类非常隐蔽的 bug不需要任何额外成本。第四如果子组件里发现通过多个 props 联动计算逻辑越来越复杂先停下来想想是不是应该把这块逻辑抽到子组件内部用一个 computed 处理或者抽成一个组合式函数不管理解了多少高级 API最终比拼的还是谁能把代码组织得更简单、更可读。第五代码评审时多问一句这个通信能去掉吗。很多时候父子组件通信复杂是因为组件拆分本身就有问题该合的两个组件拆了该拆的没拆。如果通信成本变得很高回头审视组件粒度往往比继续打补丁更有效。这些习惯看着琐碎但它们保证了我接手任何项目、面对任何组件时都能快速理解数据是怎么流动的改动时也不用担心波及一堆意想不到的地方。Vue 的父子组件通信看似是入门知识点但真正能在项目里稳定、高效地用好它靠的正是这些细节上的克制与一致。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑