props/$emit

174 阅读1分钟

概述

props/$emit 是父子组件通讯的一种方式。

父组件通过 v-bind 绑定 propv-on 监听一个自定义事件。

<my-child :message="message" @myChange="change"></my-child>

子组件通过 props 接收父组件绑定的 prop,使用 $emit 触发父组件监听的自定义函数。

// 接收值。
props: ["message"]

// 触发事件。
$emit("myChange");

通过以上步骤就能够实现父子组件的交互了。

示例:

  • 父组件:
<template>
  <my-child :message="message" @myChange="change"></my-child>
</template>

<script>
export default {
  data() {
    return {
      message: "",
    };
  },
  methods: {
    change() {
      console.log("change");
    },
  },
};
</script>
  • 子组件:
<template>
  <div>
    {{ message }}
  </div>
</template>

<script>
export default {
  // 接收值。
  props: ["message"],

  // 触发父组件监听的自定义事件。
  mounted() {
    this.$emit("myChange");
  },
};
</script>

单项数据流

父组件向子组件传递数据时,数据只能从父组件流向子组件,而不能反过来。

也就是说子组件不能直接修改 Props 接收过来的数据。只能通过 $emit 触发事件告知父组件进行修改。

父组件的数据发生更新后又会将最新的数据流下到子组件中。

如果接收的 Prop 是个对象类型的数据,可以对其 property 进行过修改。只要不修改 Prop 的引用地址 Vue 就不会报错。

如果 Prop 类型是基本数据类型,那么直接修改 Prop 会报错。

总的来说还是不推荐子组件直接修改 Prop,这样会导致数据混淆。

Props

Props

Props 定义接收父组件通过 v-bind 绑定到自身的值,只有通过 Props 定义接收后子组件才能在实例中通过 this 去访问。例如,父组件通过 v-bind:msg="Hello,Vue!",子组件通过 Props 定义接收后,就能通过 this.msg 获取到传递过来的值了。

Props 的值可以是 数组 或是 对象

数组形式:

// 和 data 数据同级定义。
props: ["msg", "isOk"] // 在数组中定义需要接收的参数

Props 验证

通过设置规则,规定父元素传递过来的数据类型。该模式下的 Props 值是一个 对象

props: {
    // 规定 msg 必须是字符串类型。
    msg: String,
    
    // 规定 count 必须是字符串或是数值类型。
    count: [String, Number] // 多个类型需要用数组包裹。 
}

上面例子中有 单个类型多个类型 的情况。

Props 中的每一项 Prop 也可以设置为对象类型。

props: {
    msg: {
        type: String
    },
    
    count: {
        type: [String, Number]
    }
}

对象类型下,type 属性描述的就是 Prop 的类型。

在对象类型下还可以配置 required(必传)default(默认值) 选项。

props: {
    msg: {
        type: String,
        required: true // 必传
    },
    count: {
        type: [String, Number],
        default: 0 // 如果不传默认值为 0
    }
}

如果 default(默认值) 设置的是 对象数组 类型,需要通过一个工厂函数返回。

props: {
    data: {
        type: Object,
        default: () => ({})  // 工厂函数返回
    },
    list: {
        type: Array,
        default: () => []  // 工厂函数返回
    }
}

在父组件没有传递数据时,子组件通过 Props 接收了性但是没有设置默认值的情况下,这个 Prop 的值拿到就会是 undefined

但是这种情况对于 布尔类型 除外,如果定义了 Prop布尔类型 ,而父组件没有传递,那么这个 Prop 取到的值是 false,除非显式通过 default 设置其默认值为 undefined。否则 布尔类型 在这种情况取到的值是 false

其目的是为了贴合 HTML5 的规范。

不传:

<my-child></my-child>

<!--等同与-->
<my-child :isOk="false"></my-child>

传:

<my-child isOk></my-child> 

<!--等同与-->
<my-child :isOk="true"></my-child>

就好像 input 标签中的 disabled 属性一样,可以简写。

不被 Props 接收的属性

父组件绑定的数据,子组件未定义 Prop 接收,那么这个属性就会挂到子组件的根元素上。这种行为称为 Props透传

父组件:

<template>
  <div>
    <Child msg="Hello,Vue!" class="text-wrap"/>
  </div>
</template>

<script>
import Child from "@/components/Child.vue";
export default {
  components: {
    Child,
  },
};
</script>

子组件:

<template>
  <div class="child-container">我是子组件</div>
</template>

<script>
export default {};
</script>

子组件没有定义 Prop 接收父组件绑定的值,这些绑定的属性自动挂到子组件的根元素上。并对 ClassStyle 的冲突做合并。

上面例子中父元素绑定的 class="text-wrap" 会和子组件根节点的 class="child-container" 合并成 class="child-container text-wrap"

阻止 Props 透传

如果不希望组件的根节点接受透传属性,那么可以在组件的实例中设置 inheritAttrs: false

ClassStyle 不会受到 inheritAttrs: false 的影响。

父组件:

<template>
  <div>
    <Child msg="Hello,Vue!" class="text-wrap"/>
  </div>
</template>

<script>
import Child from "@/components/Child.vue";
export default {
  components: {
    Child,
  },
};
</script>

子组件:

<template>
  <div class="child-container">我是子组件</div>
</template>

<script>
export default {
  // 不接受透传属性,但是 Class 和 Style 不受影响。
  inheritAttrs: false,
};
</script>

$attrs

子组件未定义 Prop 接收的属性,都可以在 $attrs 中访问到。

子组件:

<template>
  <div class="child-container">我是子组件</div>
</template>

<script>
export default {
  inheritAttrs: false,
  created() {
    console.log(this.$attrs, "$attrs"); // { msg: "Hello,Vue!" }
  },
};
</script>

$emit

使用

$emit 它的作用是通过触发实例上的自定义事件,从而向父组件传递消息。父组件可以监听这个事件并做出相应的处理。

$emit 是一个方法,它的第一个参数是 事件名称,其后面所有传入的参数都会作为向父组件传递的数据。

父元素:

<template>
  <div>
    <Child @child-change="handleChange"/>
  </div>
</template>

<script>
import Child from "@/components/Child.vue";
export default {
  components: {
    Child,
  },
  methods: {
    handleChange(person, msg) {
      console.log(person, "person");
      console.log(msg, "msg");
    }
  }
};
</script>

子组件:

<template>
  <div class="child-container">我是子组件</div>
</template>

<script>
export default {
  mounted() {
    this.$emit("child-change", { name: "张三" }, "Hello,Vue!");
  }
};
</script>

事件名

事件名不存在任何自动化的大小写转换。例如,子组件触发了一个小驼峰命名的事件名 myEvent

this.$emit('myEvent');

那父元素就必须要监听 myEvent 这个名字的事件。

<Child @myEvent="handleEvent"/>

<!-- 短横线命名(Kebab Case)-->
<Child @my-event="handleEvent"/>

因为事件名不存在任何大小写转换,所以上面例子中的监听到的 my-event 事件不会被触发。

事件名规范更推荐使用 短横线命名(Kebab Case),也就是上面例子中的 my-event 的命名方式。