查看: 1787|回复: 3

Vue父子组件传值与provide/inject页面刷新

[复制链接]
发表于 前天 14:00 | 显示全部楼层 |阅读模式
原文围绕 Vue 2 场景下的父子组件传值和列表刷新展开。示例使用 Element UI 的 Dialog、.sync 和 provide/inject。这里的“页面刷新”不是 window.location.reload,而是在新增、删除或编辑成功后重新调用列表接口,让当前列表获取最新数据。下面按传值、事件回传、ref 调用、provide/inject 刷新、跨多层组件刷新带参数来整理。

一、父组件向子组件传值:props 接收
父组件通过动态绑定把 data 传给子组件,子组件用 props 接收。示例:
  1. <!-- Parent.vue -->
  2. <template>
  3.   <Son :msg='msg'></Son>
  4. </template>
  5. <script>
  6. export default {
  7.   name: 'Parent',
  8.   data () {
  9.     return {
  10.       msg: '父组件传过来的信息'
  11.     }
  12.   }
  13. }
  14. </script>
复制代码
  1. <!-- Son.vue -->
  2. <template>
  3.   <div>父组件传递过来的数据:{{ msg }}</div>
  4. </template>
  5. <script>
  6. export default {
  7.   name: 'Son',
  8.   props: ['msg']
  9. }
  10. </script>
复制代码

注意:子组件不要直接修改 prop。原文遇到 Element Dialog 场景时出现警告:Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. 原因就是子组件修改了父组件传入的值。推荐用 computed 的 get/set 中转:get 返回 prop,set 通过 $emit 通知父组件更新。
  1. computed: {
  2.   SonValue: {
  3.     get () {
  4.       return this.msg
  5.     },
  6.     set (newValue) {
  7.       this.$emit('update:msg', newValue)
  8.     }
  9.   }
  10. }
复制代码

二、子组件向父组件传值:$emit 与 .sync
子组件通过 this.$emit('自定义事件', 值) 把数据回传父组件。父组件需要监听事件并处理。若父组件使用 .sync 修饰符,则子组件触发 update:msg 即可,父组件不必再单独写处理函数。
  1. <!-- Parent.vue -->
  2. <template>
  3.   <Son :msg.sync='msg'></Son>
  4. </template>
  5. <script>
  6. export default {
  7.   name: 'Parent',
  8.   data () {
  9.     return {
  10.       msg: '父组件传过来的信息'
  11.     }
  12.   }
  13. }
  14. </script>
复制代码
  1. <!-- Son.vue -->
  2. <template>
  3.   <div>
  4.     父组件传递过来的数据:{{ msg }}
  5.     <button @click='passValue'>传递值给父组件</button>
  6.   </div>
  7. </template>
  8. <script>
  9. export default {
  10.   name: 'Son',
  11.   props: ['msg'],
  12.   methods: {
  13.     passValue () {
  14.       this.$emit('update:msg', '这是回传给父组件的值')
  15.     }
  16.   }
  17. }
  18. </script>
复制代码

这里的 .sync 本质是 :msg 加 @update:msg 的语法糖。原文示例使用 Vue 2 写法,迁移到其他版本时要按目标版本的组件通信方式调整。

三、用 ref 调用子组件方法
ref 用在普通 DOM 上时,this.$refs.myDiv 拿到 DOM 元素;用在子组件上时,拿到子组件实例,可以调用子组件方法。原文给出的基本形式是:
  1. <div ref='myDiv'>这是一个 div 盒子</div>
复制代码
  1. this.$refs.myDiv
复制代码

子组件场景:
  1. this.$refs.组件ref名.组件方法名()
复制代码

这个能力适合父组件需要主动触发子组件动作的场景,例如让弹窗关闭、让列表组件重新取数。使用时要注意 ref 名称与模板中一致,并且目标方法要在子组件 methods 中定义。

四、provide/inject 实现无参数刷新
原文推荐的无参数刷新思路是:父组件提供获取列表的方法,子组件注入该方法;子组件在 Dialog 确认并完成新增后调用注入方法,让父组件重新请求列表。原文示例中的 provide 按可运行写法整理为函数返回对象:
  1. <!-- Parent.vue -->
  2. <template>
  3.   <div class='parent'>
  4.     <el-table :data='data'></el-table>
  5.     <Son :dialogValue.sync='dialogValue'></Son>
  6.   </div>
  7. </template>
  8. <script>
  9. import Son from './Son.vue'
  10. export default {
  11.   name: 'Parent',
  12.   components: { Son },
  13.   data () {
  14.     return {
  15.       data: [],
  16.       dialogValue: false
  17.     }
  18.   },
  19.   provide () {
  20.     return {
  21.       getListApi: this.getListApi
  22.     }
  23.   },
  24.   methods: {
  25.     async getListApi () {
  26.       const tableList = await this.$axios({
  27.         url: 'http://xxx.xxxx.xxx',
  28.         method: 'GET'
  29.       })
  30.       this.data = tableList
  31.     }
  32.   }
  33. }
  34. </script>
复制代码
  1. <!-- Son.vue -->
  2. <template>
  3.   <div class='son'>
  4.     <el-dialog
  5.       title='提示'
  6.       :visible.sync='dialogVisibleShow'
  7.       width='30%'
  8.       :before-close='handleClose'>
  9.       <span>这是一段信息</span>
  10.       <span slot='footer' class='dialog-footer'>
  11.         <el-button @click='dialogVisibleShow = false'>取 消</el-button>
  12.         <el-button type='primary' @click='insertData'>确 定</el-button>
  13.       </span>
  14.     </el-dialog>
  15.   </div>
  16. </template>
  17. <script>
  18. export default {
  19.   name: 'Son',
  20.   props: ['dialogValue'],
  21.   inject: ['getListApi'],
  22.   computed: {
  23.     dialogVisibleShow: {
  24.       get () {
  25.         return this.dialogValue
  26.       },
  27.       set (newValue) {
  28.         this.$emit('update:dialogValue', newValue)
  29.       }
  30.     }
  31.   },
  32.   methods: {
  33.     insertData () {
  34.       this.getListApi()
  35.       this.$emit('update:dialogValue', false)
  36.     },
  37.     handleClose (done) {
  38.       done()
  39.     }
  40.   }
  41. }
  42. </script>
复制代码

关键点有两个:一是子组件通过 computed 的 set 触发 update:dialogValue,不直接改 prop;二是新增成功后调用 inject 注入的 getListApi,使父组件列表重新取数。

五、跨多层组件刷新带参数:A 通知 B 刷新
原文用 A、B、C 三层组件说明复杂用法:A 是 B 的父组件,B 是 C 的父组件。B 是 A 的 Dialog,B 中某一项点击编辑后弹出 C Dialog;C 点击确定并成功插入数据后,需要重新获取 B 的列表数据。

原文给出的流程是:
1. B 中点击编辑后,通过 this.$emit('自定义事件', item) 通知 A,A 把 item 存入 data 中的变量。
2. A 使用 provide 注入一个方法,该方法内部通过 this.$refs.<ref名>.方法名(data 中存储的 item) 调用 B 的取数方法。
3. B 定义获取列表数据的方法,方法名与第 2 步调用的名称一致。
4. C 中 inject A 注入的方法;C 点击确定后调用该方法,A 再调用 B 的取数方法,B 重新获取列表。

这个方案的核心不是“刷新整个浏览器页面”,而是用 provide/inject 把刷新动作从深层组件传回目标列表组件。参数需要提前保存在 A 的 data 中,否则 C 触发刷新时无法把 B 需要的 item 带给列表请求。对于组件层级较深、又不想引入 Vuex 的场景,这是原文给出的可落地做法;如果只是父子两层,直接 $emit 或 ref 会更直观。

六、排查与适用边界
常见问题包括:props 被直接修改导致警告;$emit 的事件名与父组件监听名不一致;.sync 场景没有触发 update:xxx;ref 名称写错或调用时机早于子组件渲染;inject 注入的方法没有在父级 provide 中提供;刷新方法只关闭弹窗却没有重新请求列表。排查时先确认数据流向,再确认事件名、ref 名、provide 键名和方法名是否一一对应,最后检查接口调用是否在新增成功之后执行。

总结:Vue 父子组件通信优先用 props 下行、$emit 上行,需要双向同步时可用 .sync 的 update:事件;页面刷新优先封装列表请求方法,通过 ref 或 provide/inject 触发重新取数。原文示例基于 Vue 2 与 Element UI 的常见写法,迁移版本时应同步调整组件通信语法。
回复

使用道具 举报

发表于 前天 19:00 | 显示全部楼层

Re: Vue父子组件传值与provide/inject页面刷新

楼主整理得很清楚,特别是开头把“页面刷新”界定成重新请求列表接口而不是 location.reload,这点挺关键的,很多刚接触 Vue 的人容易把两者混在一起。前面 props、.sync、ref 这几块也讲得比较实在,computed 的 get/set 中转那个写法确实比直接在子组件里改 prop 规范得多,Dialog 场景下基本躲不开这个警告。 顺着 provide/inject 这块补充几点自己踩过的坑: 一是 provide 用函数返回对象这个写法是对的,Vue 2 里如果直接写对象,注入方拿到的值不会跟着变。不过传方法本身一般没这个问题,this.getListApi 定义在 methods 上时 this 已经指向组件实例,直接传引用就能调。但如果后面要带参数或者做一层包装,建议写成 getListApi: () => this.getListApi(),避免回调里 this 跑偏。 二是子组件 inject 的时候最好给个默认值,比如 inject: { getListApi: { default: () => {} } },这样子组件被单独复用或者脱离父级时不会直接报错,排查起来也省事。 三是跨多层带参数刷新的话,注入函数直接接收参数就行,参数多了可以考虑传一个对象进去,比一个个位置参数好维护。另外父组件方法里建议把请求和赋值分开写,await 之后先判空再赋
回复 支持 反对

使用道具 举报

发表于 前天 19:10 | 显示全部楼层

Re: Vue父子组件传值与provide/inject页面刷新

整理得挺清楚,尤其把“页面刷新”和 window.location.reload 区分开这一点很关键,很多新手一说刷新就真去刷浏览器,结果弹窗状态、查询条件全丢了。按“重新调列表接口”来理解才是对的。 几个点我觉得挺实用: 一是 props 那段提醒不要直接改 prop,配上 computed 的 get/set 中转,这个写法在 Dialog 里特别常见。Element 的 el-dialog 用 visible.sync 时,子组件如果直接 this.visible = false 就会出那个 Avoid mutating a prop 的警告,用 update:xxx 事件回传就干净了,跟 .sync 是配套的。 二是 provide 用函数返回对象这个细节值得强调一下。如果写成对象字面量,this 的指向和注入时机容易出问题,函数形式里 this 指向组件实例,拿 getListApi 更稳妥。另外提一句,provide/inject 本身不是响应式的,这里只是把方法传下去调用,所以没问题;但如果是传 data 下去,子组件拿到的就不是实时值了,这点容易踩。 三是用 ref 调子组件方法来做刷新,和 provide/inject 其实各有适用场景。层级浅、关系明确的父子,ref 更直观,也好调试;层级一深,中间隔了好几层组件,再一层层 $emit 上去就太啰嗦了,这时候 p
回复 支持 反对

使用道具 举报

发表于 前天 19:20 | 显示全部楼层

Re: Vue父子组件传值与provide/inject页面刷新

整理得挺全面的,props 不能直接改、用 computed 的 get/set 中转这点确实容易踩坑,尤其 Dialog 里直接改传入的 visible 值,那个 “Avoid mutating a prop directly” 的警告基本都会遇到,用 .sync 配 update:xxx 就顺很多。 provide/inject 那段思路很实用,列表页套着多层弹窗表单时,比一层层 $emit 往上抛清爽不少。补充一点:provide 里直接返回 this.getListApi 这种写法是可以的,Vue 2 的 methods 在初始化时已经绑定过 this,子组件 inject 后直接调用不会丢上下文;如果改成普通函数或用箭头函数再包一层,就得自己留意 this 指向了。 另外无参数刷新适合新增成功后整表重取,但如果编辑、删除后想带上当前分页和筛选条件,可以让注入的方法接收参数,父组件里把当前查询条件合并进去,子组件只传变化的部分,这样接口调用更可控。provide/inject 本身不是响应式的,不过这里传的是方法而不是数据,所以没影响。 ref 那条也提醒得对,父组件调子组件方法简单直接,但最好放在 mounted 之后或由事件触发,避免子组件还没渲染完就取实例。整体按耦合度从低到高选就行:常规通信走 props/$emit,父调子动作用 ref,跨层刷新用 provide
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-10-27 16:30 , Processed in 0.028013 second(s), 29 queries , Gzip On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部