原文围绕 Vue 2 场景下的父子组件传值和列表刷新展开。示例使用 Element UI 的 Dialog、.sync 和 provide/inject。这里的“页面刷新”不是 window.location.reload,而是在新增、删除或编辑成功后重新调用列表接口,让当前列表获取最新数据。下面按传值、事件回传、ref 调用、provide/inject 刷新、跨多层组件刷新带参数来整理。
一、父组件向子组件传值:props 接收
父组件通过动态绑定把 data 传给子组件,子组件用 props 接收。示例:
- <!-- Parent.vue -->
- <template>
- <Son :msg='msg'></Son>
- </template>
- <script>
- export default {
- name: 'Parent',
- data () {
- return {
- msg: '父组件传过来的信息'
- }
- }
- }
- </script>
复制代码- <!-- Son.vue -->
- <template>
- <div>父组件传递过来的数据:{{ msg }}</div>
- </template>
- <script>
- export default {
- name: 'Son',
- props: ['msg']
- }
- </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 通知父组件更新。
- computed: {
- SonValue: {
- get () {
- return this.msg
- },
- set (newValue) {
- this.$emit('update:msg', newValue)
- }
- }
- }
复制代码
二、子组件向父组件传值:$emit 与 .sync
子组件通过 this.$emit('自定义事件', 值) 把数据回传父组件。父组件需要监听事件并处理。若父组件使用 .sync 修饰符,则子组件触发 update:msg 即可,父组件不必再单独写处理函数。
- <!-- Parent.vue -->
- <template>
- <Son :msg.sync='msg'></Son>
- </template>
- <script>
- export default {
- name: 'Parent',
- data () {
- return {
- msg: '父组件传过来的信息'
- }
- }
- }
- </script>
复制代码- <!-- Son.vue -->
- <template>
- <div>
- 父组件传递过来的数据:{{ msg }}
- <button @click='passValue'>传递值给父组件</button>
- </div>
- </template>
- <script>
- export default {
- name: 'Son',
- props: ['msg'],
- methods: {
- passValue () {
- this.$emit('update:msg', '这是回传给父组件的值')
- }
- }
- }
- </script>
复制代码
这里的 .sync 本质是 :msg 加 @update:msg 的语法糖。原文示例使用 Vue 2 写法,迁移到其他版本时要按目标版本的组件通信方式调整。
三、用 ref 调用子组件方法
ref 用在普通 DOM 上时,this.$refs.myDiv 拿到 DOM 元素;用在子组件上时,拿到子组件实例,可以调用子组件方法。原文给出的基本形式是:
- <div ref='myDiv'>这是一个 div 盒子</div>
复制代码
子组件场景:
- this.$refs.组件ref名.组件方法名()
复制代码
这个能力适合父组件需要主动触发子组件动作的场景,例如让弹窗关闭、让列表组件重新取数。使用时要注意 ref 名称与模板中一致,并且目标方法要在子组件 methods 中定义。
四、provide/inject 实现无参数刷新
原文推荐的无参数刷新思路是:父组件提供获取列表的方法,子组件注入该方法;子组件在 Dialog 确认并完成新增后调用注入方法,让父组件重新请求列表。原文示例中的 provide 按可运行写法整理为函数返回对象:
- <!-- Parent.vue -->
- <template>
- <div class='parent'>
- <el-table :data='data'></el-table>
- <Son :dialogValue.sync='dialogValue'></Son>
- </div>
- </template>
- <script>
- import Son from './Son.vue'
- export default {
- name: 'Parent',
- components: { Son },
- data () {
- return {
- data: [],
- dialogValue: false
- }
- },
- provide () {
- return {
- getListApi: this.getListApi
- }
- },
- methods: {
- async getListApi () {
- const tableList = await this.$axios({
- url: 'http://xxx.xxxx.xxx',
- method: 'GET'
- })
- this.data = tableList
- }
- }
- }
- </script>
复制代码- <!-- Son.vue -->
- <template>
- <div class='son'>
- <el-dialog
- title='提示'
- :visible.sync='dialogVisibleShow'
- width='30%'
- :before-close='handleClose'>
- <span>这是一段信息</span>
- <span slot='footer' class='dialog-footer'>
- <el-button @click='dialogVisibleShow = false'>取 消</el-button>
- <el-button type='primary' @click='insertData'>确 定</el-button>
- </span>
- </el-dialog>
- </div>
- </template>
- <script>
- export default {
- name: 'Son',
- props: ['dialogValue'],
- inject: ['getListApi'],
- computed: {
- dialogVisibleShow: {
- get () {
- return this.dialogValue
- },
- set (newValue) {
- this.$emit('update:dialogValue', newValue)
- }
- }
- },
- methods: {
- insertData () {
- this.getListApi()
- this.$emit('update:dialogValue', false)
- },
- handleClose (done) {
- done()
- }
- }
- }
- </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 的常见写法,迁移版本时应同步调整组件通信语法。 |