为了账号安全,请及时绑定邮箱和手机立即绑定

【面试趣谈】Vue中父子组件通信6种方式,你都掌握了吗?

标签:
Vue.js

父子组件通信的所有方式?

1、Props + $emit( )

父组件向子组件传递数据

我们在父组件(Parent.vue)中定义一个 name 数据,通过 v-bind 指令将 name 重命名为 parentName 传递给子组件

<template>
  <h1>父组件</h1>
  <p>{{ name }}</p>
  <Child :parentName="name"></Child>
</template>
<script>
  import Child from './Child.vue';
  export default {
    components: {
      Child,
    },
    data() {
      return {
        name: '周帅帅'
      }
    }
  }
</script>

我们在子组件(Child.vue)中使用 props 方法接收父组件传递的参数 parentName,除了接收值为数组外,接收值还可以为对象,并且还可以设置默认值。

<template>
  <div>我是子组件</div>
  <p>{{ parentName }}</p>
</template>
<script>
  export default {
    // 还可以定义接收的类型
    // props: { parentName: { type: String, default: '' } }
    props: ['parentName'],
  }
</script>

子组件向父组件传递数据

我们在子组件(Child.vue)中添加 button 按钮, 点击时我们使用 $emit() 的方式将 handleClick 事件重命名为 childMessage 方法传递给父组件

<template>
  <div>我是子组件</div>
  <p>{{ parentName }}</p>
  <button @click="handleClick">按钮</button>
</template>
<script>
  export default {
    props: ['parentName'],
    methods: {
      handleClick() {
        this.$emit('childMessage', '子组件的值')
      }
    }
  }
</script>

我们在父组件(Parent.vue)接收子组件(Child.vue)传递的 childMessage 方法,并重新命名为 parentClick 方法,并将子组件传递在 $event 的值赋值给 name,这样我们发现父组件中的 name 值发生了改变。

<template>
  <h1>父组件</h1>
  <p>{{ name }}</p>
  <Child :parentName="name" @parentClick="childMessage"></Child>
  <!-- 下面的这行代码和上面代码意思是一样的 -->
  <!-- <Child :parentName="name" @childMessage="name = $event"></Child> -->
</template>
<script>
  import Child from './Child.vue';
  export default {
    components: {
      Child,
    },
    data() {
      return {
        name: '周帅帅'
      }
    },
    methods: {
      parentClick($event) {
        this.name = $event
      }
    }
  }
</script>

2、使用回调函数(callback)方式

我们在父组件(Parent.vue)中定义一个 changeMessage 来修改 name 的值,然后将 parentClick 方法和 parentName 的值传递给子组件(Child.vue)

<template>
  <h1>父组件</h1>
  <div>{{ name }}</div>
  <Child :parentName="name" :parentClick="changeMessage"></Child>
</template>
<script>
  import Child from './Child.vue';
  export default {
    components: {
      Child,
    },
    data() {
      return {
        name: '周帅帅'
      }
    },
    methods: {
      changeMessage() {
        this.name = 'web前端'
      }
    }
  }
</script>

我们在子组件(Child.vue)通过 props 接收父组件传递的 msgparentClick 方法

<template>
  <h1>我是子组件</h1>
  <p>{{parentName}}</p>
  <button @click="parentClick">按钮</button>
</template>
<script>
  export default {
    props: ['parentName', parentClick],
  }
</script>

这样当我们点击按钮的时候,发现父组件中的 name 和子组件中的 parentName 都会发生改变。

3、$children + $parent

我们在父组件(Parent.vue)通过 this.$children[0].age 获取子组件(Child.vue)中 age 的值并修改为50

<template>
  <h1>父组件</h1>
  <p>{{ name }}</p>
  <Child @click="changeChildAge">按钮</Child>
</template>
<script>
  import Child from './Child.vue';
  export default {
    components: {
      Child,
    },
    data() {
      return {
        name: '周帅帅'
      }
    },
    methods: {
      changeChildAge() {
        // 因为子组件可能存在多个,所以加了一个下标0
        this.$children[0].age = 50;
      }
    }
  }
</script>

我们在子组件(Child.vue)通过 this.$parent.message 获取父组件中的值,并在子组件中展示出来

<template>
  <div>
    <div>我是子组件</div>
    <p>{{ age }}</p>
    <p>{{ parentName }}</p>  
  </div>
</template>
<script>
  export default {
    data() {
      return {
        age: 10
      }
    },
    computed: {
      parentName() {
        return this.$parent.name
      }
    }
  }
</script>

所以在子组件(Child.vue)中可以显示父组件的 name 值,当父组件中的按钮点击时,子组件(Child.vue)中 age 的值会从10变成50

4、provide + inject

我们在父组件(Parent.vue)里面通过 provide 提供一个 message 的值

<template>
  <h1>父组件</h1>
  <Child></Child>
</template>
<script>
  import Child from './Child.vue';
  export default {
    components: {
      Child,
    },
    provide: {
      name: '周帅帅'
    }
  }
</script>

子组件(Child.vue)中通过 inject 接收父组件(Parent.vue)中传递的值

<template>
  <div>
    <h1>子组件</h1>
    {{ name }}
  </div>
</template>

<script>
  export default {
    inject: ['name']
  }
</script>

这样我们可以在子组件(Child.vue)中看到父组件中定义的 name

5、$attrs + $listeners

我们在父组件(Parent.vue)中向子组件(Child.vue)传递 nameage,我们在父组件(Parent.vue)中定义了一个 parentName 事件监听器

<template>
  <h1>父组件</h1>
  <p>姓名:{{ name }}</p>
  <p>年龄:{{ age }}</p>
  <Child :name="name" :age="age" @parentName="changeName"></Child>
</template>
<script>
  import Child from './Child.vue';
  export default {
    components: {
      Child,
    },
    data() {
      return {
        name: '周帅帅',
        age: 24
      }
    },
    methods: {
      changeName() {
        this.name = '小夏'
      }
    }
  }
</script>

我们在子组件(Child.vue)中可以通过 $listeners 获取到父组件(Parent.vue)中的 parentName,这样我们就能执行父组件(Parent.vue)中绑定的事件处理函数(changeName)。然后我们在子组件(Child.vue)里面引入孙子组件(GrandChild.vue),将父组件(Parent.vue)传递给子组件(Child.vue)的 nameage 通过 v-bind="$attrs" 传递给孙子组件(GrandChild.vue)。

<template>
  <div>
    <h1>子组件</h1>
    <button @click="$listeners.parentName">按钮</button>
    // $attrs相当于子组件向孙子组件传递:name="name" :age="age"
    <GrandChild v-bind="$attrs"></Child>
  </div>
</template>
<script>
  import GrandChild from './GrandChild.vue';
  export default {
    components: {
      GrandChild,
    },
    data() {
      return {
        
      }
    }
  }
</script>

这样我们在孙子组件(GrandChild.vue)中就可以接收子组件(Child.vue)传递过来的 nameage 了。然后通过 $attrs.name$attrs.age 来使用。

<template>
  <div>
    <h1>孙子组件</h1>
    <p>姓名:{{ $attrs.name }}</p>
    <p>年龄:{{ $attrs.age }}</p>
  </div>
</template>
<script>
  export default {
    data() {
      return {
        // 如果不加这个属性,我们发现div中会携带name和age这两个属性
        inheritAttrs: false
      }
    }
  }
</script>

6、ref

我们在父组件(Parent.vue)中给引入子组件(Child.vue),并且给子组件(Child.vue)中定义一个 refchildName 的值。

<template>
  <h1>父组件</h1>
  <Child ref="childName"></Child>
  <button @click="changeName">按钮</button>
</template>
<script>
  import Child from './Child.vue';
  export default {
    components: {
      Child,
    },
    methods: {
      changeName() {
        console.log('修改前', this.$refs.childName.name)
        this.$refs.childName.changeName()
        console.log('修改后', this.$refs.childName.name)
      }
    }
  }
</script>

我们在子组件(Child.vue)中定义一个 changeName 的方法

<template>
  <div>
    <h1>子组件</h1>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        name: '周帅帅'
      }
    },
    methods: {
      changeName() {
        this.name = '前端程序员'
      }
    }
  }
</script>

这样当我们点击父组件(Parent.vue)中的按钮时,我们在浏览器的控制台就可以看到打印的值发生了变化。

几种父子传递方法的区别

props

props 只能用于父组件传递给子组件的时候区使用。

provide与inject?

我们使用 provideinject 进行传值可以在父子组件中传递,也可以将父子组件的传递的值在孙子组件中接收,例子在下面。

举个例子

父组件(Parent.vue)里调用了子组件(Child.vue),同时子组件(Child.vue)里调用了孙子组件(GrandChild.vue)

区别

父组件(Parent.vue)可以用 props 方法直接传数据给子组件(Child.vue),但不能传数据给孙子组件(GrandChild.vue)
父组件(Parent.vue)可以用 provideinject 方法直接传递数据给子组件(Child.vue),也可以直接给孙子组件(GrandChild.vue)

官方说明provide和inject

  • 类型:
    • provide:Object | () => Object
    • inject:Array<string> | { [key: string]: string | Symbol | Object }
  • 详细:这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。provide 选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的 property。在该对象中你可以使用 ES2015 Symbols 作为 key,但是只在原生支持 SymbolReflect.ownKeys 的环境下可工作。inject 选项应该是:
    • 一个字符串数组,或
    • 一个对象,对象的 key 是本地的绑定名,value 是:
      • 在可用的注入内容中搜索用的 key (字符串或 Symbol),或
      • 一个对象,该对象的:
        • from property 是在可用的注入内容中搜索用的 key (字符串或 Symbol)
        • default property 是降级情况下使用的 value

提示

provideinject 绑定并不是可响应的。这是刻意为之的也就是说当我们修改子组件中的值父组件的值不会跟着改变。然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。

案例:

// 父级组件提供 'foo'
var Provider = {
  provide: {
    foo: 'bar'
  },
  // ...
}

// 子组件注入 'foo'
var Child = {
  inject: ['foo'],
  created () {
    console.log(this.foo) // => "bar"
  }
  // ...
}

利用 ES2015 Symbols、函数 provide 和对象 inject

const s = Symbol()

const Provider = {
  provide () {
    return {
      [s]: 'foo'
    }
  }
}

const Child = {
  inject: { s },
  // ...
}

接下来 2 个例子只工作在 Vue 2.2.1 或更高版本。低于这个版本时,注入的值会在 propsdata 初始化之后得到。

使用一个注入的值作为一个 property 的默认值:

const Child = {
  inject: ['foo'],
  props: {
    bar: {
      default () {
        return this.foo
      }
    }
  }
}

使用一个注入的值作为数据入口:

const Child = {
  inject: ['foo'],
  data () {
    return {d
      bar: this.foo
    }
  }
}

2.5.0+ 的注入可以通过设置默认值使其变成可选项:

const Child = {
  inject: {
    foo: { default: 'foo' }
  }
}

如果它需要从一个不同名字的 property 注入,则使用 from 来表示其源 property

const Child = {
  inject: {
    foo: {
      from: 'bar',
      default: 'foo'
    }
  }
}

prop 的默认值类似,你需要对非原始值使用一个工厂方法:

const Child = {
  inject: {
    foo: {
      from: 'bar',
      default: () => [1, 2, 3]
    }
  }
}

$attrs与$listeners?

$attrs

包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (classstyle 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (classstyle 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。我们需要注意的是 inheritAttrs 属性,当我们设置这个属性为 inheritAttrs: false时,就可以避免父作用域中的 $attrs 上的属性绑定到子组件的根元素上。

$listeners

包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用。

$parent$childrenprops 的区别?

  1. props 仅仅只能接收,单项绑定。着重数据传递。向子组件传递数据。
  2. $parent 既可以接受父组件数据 ,又可以修改父组件数据,是双向的 。
  3. $parent 还可以调用父组件的方法。

$children的缺点?

由于在一个 vue 实例中,它的子组件个数会随着需求的增多而增多,而我们在父组件想要获得某个子组件的数据或方法,是通过 this.$ children[ ] 的具体下标获取的。那么当我们在这个子组件前面再添加其它子组件,我们想要获得的子组件下标就不再是上次我们写的那个,这使得我们写死的下标具有不确定性。我们可以通过 $refs 解决这个问题,因为this.$refs 是一个对象类型,它包含所有子组件对象。

点击查看更多内容
2人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消