Skip to content

prop与emit

propemitvue组件十分重要的点,因为它们是组件之间信息传递的手段

props的概念

  • 数组Props

最基本的行为是,子组件通过一个数组定义期望的接收值,父组件通过属性名传递数据。这个数组就叫做props

在组件标签上定义的属性,如果在props数组中被定义,则放进props中,这些 props 会暴露到 this 上;如果没有,则放在该组件的根标签上(这种行为可以通过inheriAttrs关键字关闭);如果根标签也没有,则只放在$attrs中。而$attrs中的属性变量可以通过v-bind再绑定到想要绑定的标签上。这类操作情景经常会发生。

  • 对象Props

子组件定义的props键对应的值也可以是一个对象,用于更进一步的限制期望的接受值,如限制数据类型(本质是传入构造函数)、设置默认值default、验证器validator等,更多参数查阅参见官方文档。

值得注意的一点是,如果传递的是数组或者对象类型的引用型数据,则默认值应当写成函数返回值的形式,如果直接写就是传引用了,所有组件公用一个数据,就不对了。

传递的数据

数据的类型

任何类型的数据都可以被当作props期望的数据来传递,有两种特殊的情况分别是布尔值方法

  • 作为boolean传递的时候,默认为ture,所以可以把true省略,这意味我们可以不用v-on绑定语法去书写表达式得到变量真值:
vue
<!-- 仅写上 prop 但不传值,会隐式转换为 `true` -->
<BlogPost is-published />

<!-- 等价于: -->
<BlogPost :is-published="true" />

<!-- 而不是字符串: -->
<BlogPost is-published="true" />
  • 传递一个父组件的方法的时候,该方法在子组件中同样生效,即使它使用了this(挖个坑,以后了解原理):
vue
<script>
// 父组件
import TestComponent from './components/TestComponent.vue';
export default {
  components: {
    TestComponent
  },
  data() {
    return {
      name: 'xiaoyan'
    }
  },
  methods: {
    fatherFunc() {
      this.name += '233'
      console.log(this);
    }
  },
}
</script>

<template>
  {{ name }}
  <TestComponent :func="fatherFunc"></TestComponent>
</template>
vue
<template>
    <button @click="func2">子组件按钮</button>
</template>
 
<script>
// 子组件
export default {
    props: ['func'],
    data() {
        return {
            name2: 'testComponent'
        }
    },
    methods: {
        func2() {
            console.log(this)
            this.func();
        }
    }
}
</script>

特殊的引用类型数据

”单向数据流“:子组件无法通过props来修改父组件的原值,但是父组件原值的修改会传递到子组件。

对于对象这种“嵌套”引用类型的数据,”单向数据流“是不起作用的,这是因为vue只去检测最外层是否被修改,而不去递归的判断内层。

当对象或数组作为 props 被传入时,虽然子组件无法更改 props 绑定,但仍然可以更改对象或数组内部的值。 js的对象和数组是按引用传递,递归检测内部的改动有很大的性能损耗,所以Vue没有限制这个特性。

这个特性可以用在子组件想要更改父组件中的对象的时候。因为它允许了props被修改:“单向数据流”特性的特例。当然,也可以通过v-model双向数据绑定,或者emit事件传值给父组件并更改原值两种方式做到。

props单向数据流

props被建议写成HTML风格:

对于组件名我们推荐使用 PascalCase,因为这提高了模板的可读性,能帮助我们区分 Vue 组件和原生 HTML 元素。然而对于传递 props 来说,使用 camelCase 并没有太多优势,因此我们推荐更贴近 HTML 的书写风格。

props批量传递数据

可以使用不带参数的v-bind来绑定一个对象,对象中的所有属性将被传递给props

vue
<script>
export default {
  data() {
    return {
      post: {
        id: 1,
        title: 'My Journey with Vue'
      }
    }
  }
}
</script>

<template>

<BlogPost v-bind="post" />

</template>

默认情况下所有的属性(不管加不加:)都会被传递给子组件的根标签上。props相当于抽取了一部分属性作为自己的property,可以在组件中使用他们,emits也抽取了另一部分特殊的事件属性,最后剩余的$attrs被称作透传属性

v-bind绑定+inheritAttrs: false可以改变传递到根标签的默认行为,但是也只能整体的转移到另一个标签上:

vue
<template>
<div>
  <div v-bind="$attrs"></div>    
</div>
</template>
<script>
export default {
    inheritAttrs: false,
    data() {
        return {
            ...
        }
    }
}
</script>

emit

emits 选项对事件属性做了特殊处理。它会影响一个属性被解析为组件事件的监听器,还是原生 DOM 事件的监听器

具体来讲,被声明为组件事件的监听器属性(即在emits中声明)不会被传到组件的根元素上,且将从组件的 $attrs 对象中移除。取而代之的,这个被移除的函数属性可以使用方法 $emit 手动触发:

vue
<!-- 父组件 -->
<MyComponent @submit="callback" />
js
<!-- MyComponent -->
export default {
  emits: ['inFocus', 'submit'],
  methods: {
    submit() {
      this.$emit('submit') // callback()
    }
  }
}

而不在emits中声明的事件属性则会被视为原生dom的监听器,如@click不在emit中被接收。事实上,我们也不应该接收click属性,它本来开就是属于原生的,我们接收它会很容易与原生的click混淆,所以vue会抛出一个警告:

vue
<script>
export default {
    emits: ['click']
}
</script>

<template>
<button @click="click">点我通过原生click来触发自定义click事件</button>
</template>

emits除了是个数组,还可以是一个对象,这和props接收数据同理,是为了进一步限制接收事件的类型等。

$emits方法

$emits可以附带参数,表示传入函数的参数:

vue
<button @click="$emit('increaseBy', 1)">
  Increase by 1
</button>

这个方法有一个细节就是,它实现了父组件中的函数使用子组件的数据。当然,我们也可以通过props来接收父组件中的一个函数方法,然后在子组件中调用它,也可以实现相同的效果。