前言:

        在开发工程中,我们经常会遇到组件之间相互进行通信,比如在一个组件内使用多个Button,每个地方的Button展示内容不同,执行方法不同,那么我们就需要该组件传递给Button一些数据,让Button组件进行展示。又比如子组件中发生一些事件,需要由父组件来完成某些操作来修改父组件里的值,那么需要子组件向父组件传递事件以及一些参数。这儿记录一下 Vue3 中组件之间通信之props。

父子组件之间的通信方式:

  • 父组件传递给子组件: 通过 props 属性;
  • 子组件传递给父组件: 通过 $emit 触发事件;

首先先看看父组件是如何向子组件传递参数的?

什么是props?

  • props 是通过在组件上注册一些自定义的属性(attribute);
  • 通过父组件给这些属性(attribute)赋值,或者通过v-bind绑定父组件中data中的值,子组件再通过属性的名称来获取对应的值;

字符串数组用法举例:

// App.vue
<template>
  <Child :firstName="firstName" :lastName="lastName" v-bind="info"/> 
  // 当需要传递的值为对象时,可以直接通过bind绑定,也可以分开通过属性名进行绑定。
  <Child v-bing="info"></Child>
  <Child :age="info.age" :height="height"></Child>
</template>
<script>
import Child from './components/Child.vue'
export default {
  name: 'App',
  components: {
    Child
  },
  data() {
    return {
      firstName: '哈喽',
      lastName: '欢迎您!'
      info: {
          age: "18岁",
          height: '188cm'      
      }
    }
  }
}
</script>

下方是子组件:

// Child.vue
<template>
  <div>
    <h3>{{ firstName + ',' + lastName}}</h3>
    <h3>{{info.age + '的我今年身高' + info.height}}</h3>
  </div>
</template>
<script>
export default {
  name: 'Child',
  props: ["firstName", "lastName", "age", "height"]
}
</script>

通过对象绑定时,父组件可以直接使用 v-bind=“对象名” 进行传值,那么子组件同样通过对象里的属性名进行取值。

 数组用法中我们只能说明传入的属性的名称,而不能对这些属性值进行约束。

对象类型举例:

// Child.vue
<template>
  <div>
    <h3>{{ firstName + ',' + lastName}}</h3>
    <h3>{{age + '的我今年身高' + height}}</h3>
  </div>
</template>
<script>
export default {
  name: 'Child',
  props: {
    firstName: String,
    lastName: String,
    age: String,
    height: {
        type: String, // 传入的类型
        required: true, // required 为true表示该属性为必传选项 (required和default分开使用)
        default: "Hello", // 默认值,当该属性没有进行传值,该属性默认值为"Hello"
    }
  }
}
</script>

如果传入的值不符合约束类型,那么在浏览器中就会报出警告:该属性希望得到一个String类型的值,实际上得到是一个Number类型值。

通过对象形式定义props,不仅可以通过type属性来约束值的类型,还可以控制指定传入的属性是否是必传的,当属性没有传入值时属性的默认值。

下面我们再来看看type还有哪些类型?

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Data
  • Function
  • Symbol

下面简单写一下各个类型的写法:(自己也顺便加深下印象)

props: {
    obj: { // 带有默认值的对象
        type: Object,
        default() { //这儿类型为Object必须通过一个工厂函数获取,类似于data(){},防止堆地址相同
            return { grade: 90}        
        }    
    },
    funC: {
        type: Function,
        default() {
            return "Default function"  // 默认返回一个函数      
        }            
    },
    numB: { // 带有默认值的数字
        type: Number,
        default: 100    
    },
    str: { // 带有默认值的字符串
        type: String,
        default: 'heihei'     
    }
}

从上面可以看出,props有两种常见的用法:

  1. 字符串数组,数组中的字符串就是属性(attribute)的名称;(无法对传进来的值进行约束)
  2. 对象类型,对象类型我们可以在指定属性(attribute)名称的同时,指定它需要传递的类型、默认值、以及是否必须等等;

$attrs

        当我们在父组件向子组件传递了某个属性,但是在子组件中并没有通过props取值时,那么可以通过this.$attrs来获取,比较常见的是class、style、id属性等等;

举个例子:

// 父组件(父组件向子组件传递了4个属性值)
<Child :firstName="firstName" :lastName="lastName" :age="info.age" :height="info.height"></Child>
// 子组件
<template>
  <div>
    <h3>{{ firstName + ',' + lastName}}</h3>
  </div>
</template>
<script>
export default {
  name: 'Child',
  props: {
    firstName: String,
    lastName: String
  },
  created() {
      console.log(this.$attrs)  // Proxy {age: "18岁", height: "188cm", __vInternal: 1}
  }
}
</script>

所以,我们可以直接在子组件上这样写

<template>
  <div>
    <h3>{{ firstName + ',' + lastName}}</h3>
    <h3>{{ $attrs.age + '的我今年身高' + $attrs.height}}</h3>
  </div>
</template>
<script>
export default {
  name: 'Child',
  props: {
    firstName: String,
    lastName: String,
  }
}
</script>

通过$attrs一样能够使用父组件传递过来的值。注意:($attrs里包含的属性是在props 中没有定义的属性)

 当我们在父组件中传递了一个class,我们看看会发生什么?

// App.vue
<Child :firstName="firstName" :lastName="lastName" class="content"></Child>

子组件中:

<template>
  <div>
    <h3>{{ firstName + ',' + lastName}}</h3>
    <h3>{{ $attrs.age + '的我今年身高' + $attrs.height}}</h3>
  </div>
</template>
<script>
export default {
  name: 'Child',
  props: {
    firstName: String,
    lastName: String,
  }
}
</script>
<style scoped>
.content {
  color: red;
}
</style>

 当父组件再加一个属性,子组件props依然只定义(firstName, lastName)如下:

// App.vue
<Child :firstName="firstName" :lastName="lastName" :age="info.age" class="content"></Child>

我们会发现,子组件中的字体颜色变为红色,age属性加在了组件的根节点上。(我们知道template是不会加在到dom上的)

 那如果不想要这个属性继承到根节点上,怎么办呢?

 那什么时候才使用inheritAttrs,当我们希望这个属性不被根节点继承,而是需要其他节点使用这个属性,那么就可以通过设置inheritAttrs为false,然后上面我们知道了$attrs可以获取props属性以外的其他属性,那么通过$attrs来给其他节点进行绑定:

// App.vue
<Child :firstName="firstName" :lastName="lastName" v-bind="info" class="content"></Child>

子组件中:

// Child.vue
<template>
  <div>
    <h3 :class="$attrs.class">{{ firstName + ',' + lastName}}</h3>
    <h3>{{ $attrs.age + '的我今年身高' + $attrs.height}}</h3>
  </div>
</template>
<script>
export default {
  inheritAttrs: false,
  name: 'Child',
  props: {
    firstName: String,
    lastName: String,
  }
}
</script>

结果:

 还有一种情况,在vue3中可以允许有多个根节点,那么当在多个根节点时,没有通过$attrs进行绑定,那么在浏览器中会报警告:

// App.vue
<Child :firstName="firstName" :lastName="lastName" v-bind="info" class="content"></Child>
// Child.vue
<template>
  <h3>{{ firstName + ',' + lastName}}</h3>
  <h3>12312</h3>
</template>
<script>
export default {
  name: 'Child',
  props: {
    firstName: String,
    lastName: String,
  }
}
</script>

 所以,这种情况,我们必须通过$attrs来手动绑定后,才会消除警告。

// 修改Child.vue
<template>
  <h3 :class="$attrs.class">{{ firstName + ',' + lastName}}</h3>
  <h3>12312</h3>
</template>

总结:

以上就是我对父传子props的理解,记录一下,后面再总结一下$emit的用法。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐