Vue组件选项props
前面的话
组件接受的选项大部分与 Vue 实例一样,而选项 props 是组件中非常重要的一个选项。在 Vue 中,父子组件的关系可以总结为 props down, events up。父组件通过 props 向下传递数据给子组件,子组件通过 events 给父组件发送消息。本文将详细介绍 Vue 组件选项 props
父子级组件
在介绍 props 之前,先介绍父子级组件的写法
在一个良好定义的接口中尽可能将父子组件解耦是很重要的。这保证了每个组件可以在相对隔离的环境中书写和理解,也大幅提高了组件的可维护性和可重用性
【错误写法】
现在来介绍两种父子级组件的错误写法
下面这种形式的写法是错误的,因为当子组件注册到父组件时,Vue.js 会编译好父组件的模板,模板的内容已经决定了父组件将要渲染的 HTML <parent>...</parent>
运行时,它的一些子标签只会被当作普通的 HTML 来执行,<child></child> 不是标准的 HTML 标签,会被浏览器直接忽视掉
<div id="example"> <parent> <child></child> <child></child> </parent> </div>
在父组件标签之外使用子组件也是错误的
<div id="example"> <parent></parent> <child></child> </div>
【正确写法】
<div id="example"> <parent></parent> </div>
<script> var childNode = {template: '<div>childNode</div>',} var parentNode = { template: ` <div class="parent"> <child></child> <child></child> </div> `, components: {'child': childNode} }; // 创建根实例 new Vue({ el: '#example', components: {'parent': parentNode} }) </script>
静态 props
组件实例的作用域是孤立的。这意味着不能 (也不应该) 在子组件的模板内直接引用父组件的数据。要让子组件使用父组件的数据,需要通过子组件的 props 选项
使用 Prop 传递数据包括静态和动态两种形式,下面先介绍静态 props
子组件要显式地用 props
选项声明它期待获得的数据
var childNode = {template: '<div>{{message}}</div>', props:['message'] }
静态 Prop 通过为子组件在父组件中的占位符添加特性的方式来达到传值的目的
<div id="example"> <parent></parent> </div>
<script> var childNode = { template: '<div>{{message}}</div>', props:['message'] } var parentNode = { template: ` <div class="parent"> <child message="aaa"></child> <child message="bbb"></child> </div>`, components: { 'child': childNode } }; // 创建根实例 new Vue({ el: '#example', components: { 'parent': parentNode } }) </script>
命名约定
对于 props 声明的属性来说,在父级 HTML 模板中,属性名需要使用中划线写法
var parentNode = { template: ` <div class="parent"> <child my-message="aaa"></child> <child my-message="bbb"></child> </div>`, components: {'child': childNode} };
子级 props 属性声明时,使用小驼峰或者中划线写法都可以;而子级模板使用从父级传来的变量时,需要使用对应的小驼峰写法
var childNode = { template: '<div>{{myMessage}}</div>', props:['myMessage'] }
var childNode = { template: '<div>{{myMessage}}</div>', props:['my-message'] }
动态 props
在模板中,要动态地绑定父组件的数据到子模板的 props,与绑定到任何普通的 HTML 特性相类似,就是用 v-bind
。每当父组件的数据变化时,该变化也会传导给子组件
var childNode = {template: '<div>{{myMessage}}</div>', props:['myMessage'] }
var parentNode = { template: ` <div class="parent"> <child :my-message="data1"></child> <child :my-message="data2"></child> </div>`, components: {'child': childNode}, data(){ return { 'data1':'aaa', 'data2':'bbb' } } };
传递数字
初学者常犯的一个错误是使用字面量语法传递数值
<!-- 传递了一个字符串 "1" --> <comp some-prop="1"></comp>
<div id="example"> <my-parent></my-parent> </div>
<script> var childNode = { template: '<div>{{myMessage}} 的类型是 {{type}}</div>', props:['myMessage'], computed:{type(){ return typeof this.myMessage } } } var parentNode = { template: ` <div class="parent"> <my-child my-message="1"></my-child> </div>`, components: { 'myChild': childNode } }; // 创建根实例 new Vue({ el: '#example', components: { 'MyParent': parentNode } }) </script>
因为它是一个字面 prop,它的值是字符串 "1"
而不是 number。如果想传递一个实际的 number,需要使用 v-bind
,从而让它的值被当作 JS 表达式计算
<!-- 传递实际的 number --> <comp v-bind:some-prop="1"></comp>
var parentNode = { template: ` <div class="parent"> <my-child :my-message="1"></my-child> </div>`, components: {'myChild': childNode} };
或者可以使用动态 props,在 data 属性中设置对应的数字 1
var parentNode = { template: ` <div class="parent"> <my-child :my-message="data"></my-child> </div>`, components: {'myChild': childNode}, data(){ return {'data': 1} } };
props 验证
可以为组件的 props 指定验证规格。如果传入的数据不符合规格,Vue 会发出警告。当组件给其他人使用时,这很有用
要指定验证规格,需要用对象的形式,而不能用字符串数组
Vue.component('example', { props: {// 基础类型检测 (`null` 意思是任何类型都可以) propA: Number, // 多种类型 propB: [String, Number], // 必传且是字符串 propC: { type: String, required: true }, // 数字,有默认值 propD: { type: Number, default: 100 }, // 数组 / 对象的默认值应当由一个工厂函数返回 propE: { type: Object, default: function () {return { message: 'hello'} } }, // 自定义验证函数 propF: {validator: function (value) {return value > 10} } } })
type
可以是下面原生构造器
String Number Boolean Function Object Array Symbol
type
也可以是一个自定义构造器函数,使用 instanceof
检测。
当 prop 验证失败,Vue 会在抛出警告 (如果使用的是开发版本)。props 会在组件实例创建之前进行校验,所以在 default
或 validator
函数里,诸如 data
、computed
或 methods
等实例属性还无法使用
下面是一个简单例子,如果传入子组件的 message 不是数字,则抛出警告
<div id="example"> <parent></parent> </div>
<script> var childNode = {template: '<div>{{message}}</div>', props:{'message':Number} } var parentNode = { template: ` <div class="parent"> <child :message="msg"></child> </div>`, components: {'child': childNode}, data(){ return{msg: '123'} } }; // 创建根实例 new Vue({ el: '#example', components: {'parent': parentNode} }) </script>
传入数字 123 时,则无警告提示。传入字符串 '123' 时,结果如下所示
将上面代码中,子组件的内容修改如下,可自定义验证函数,当函数返回为 false 时,则输出警告提示
var childNode = {template: '<div>{{message}}</div>', props:{ 'message':{validator: function (value) {return value > 10} } } }
在父组件中传入 msg 值为 1,由于小于 10,则输出警告提示
var parentNode = { template: ` <div class="parent"> <child :message="msg"></child> </div>`, components: {'child': childNode}, data(){ return{msg:1} } };
单向数据流
prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是不会反过来。这是为了防止子组件无意修改了父组件的状态——这会让应用的数据流难以理解
另外,每次父组件更新时,子组件的所有 prop 都会更新为最新值。这意味着不应该在子组件内部改变 prop。如果这么做了,Vue 会在控制台给出警告
下面是一个典型例子
<div id="example"> <parent></parent> </div>
<script> var childNode = { template: ` <div class="child"> <div> <span> 子组件数据 </span> <input v-model="childMsg"> </div> <p>{{childMsg}}</p> </div> `, props:['childMsg'] } var parentNode = { template: ` <div class="parent"> <div> <span> 父组件数据 </span> <input v-model="msg"> </div> <p>{{msg}}</p> <child :child-msg="msg"></child> </div> `, components: {'child': childNode}, data(){ return {'msg':'match'} } }; // 创建根实例 new Vue({ el: '#example', components: {'parent': parentNode} }) </script>
父组件数据变化时,子组件数据会相应变化;而子组件数据变化时,父组件数据不变,并在控制台显示警告
修改子组件数据时,打开浏览器控制台会出现下图所示警告提示
修改 prop 数据
修改 prop 中的数据,通常有以下两种原因
1、prop 作为初始值传入后,子组件想把它当作局部数据来用
2、prop 作为初始值传入,由子组件处理成其它数据输出
[注意]JS 中对象和数组是引用类型,指向同一个内存空间,如果 prop 是一个对象或数组,在子组件内部改变它会影响父组件的状态
对于这两种情况,正确的应对方式是
1、定义一个局部变量,并用 prop 的值初始化它
props: ['initialCounter'], data: function () {return { counter: this.initialCounter} }
但是,定义的局部变量 counter 只能接受 initialCounter 的初始值,当父组件要传递的值发生变化时,counter 无法接收到最新值
<div id="example"> <parent></parent> </div> <script src="https://unpkg.com/vue"></script> <script> var childNode = { template: ` <div class="child"> <div> <span>子组件数据</span> <input v-model="temp"> </div> <p>{{temp}}</p> </div> `, props:['childMsg'], data(){ return{ temp:this.childMsg } }, }; var parentNode = { template: ` <div class="parent"> <div> <span>父组件数据</span> <input v-model="msg"> </div> <p>{{msg}}</p> <child :child-msg="msg"></child> </div> `, components: { 'child': childNode }, data(){ return { 'msg':'match' } } }; // 创建根实例 new Vue({ el: '#example', components: { 'parent': parentNode } }) </script>
下面示例中,除初始值外,父组件的值无法更新到子组件中
2、定义一个计算属性,处理 prop 的值并返回
props: ['size'], computed: {normalizedSize: function () {return this.size.trim().toLowerCase()}}
但是,由于是计算属性,则只能显示值,而不能设置值
<script src="https://unpkg.com/vue"></script> <script> var childNode = { template: ` <div class="child"> <div> <span>子组件数据</span> <input v-model="temp"> </div> <p>{{temp}}</p> </div> `, props:['childMsg'], computed:{temp(){ return this.childMsg } }, }; var parentNode = { template: ` <div class="parent"> <div> <span>父组件数据</span> <input v-model="msg"> </div> <p>{{msg}}</p> <child :child-msg="msg"></child> </div> `, components: { 'child': childNode }, data(){ return { 'msg':'match' } } }; // 创建根实例 new Vue({ el: '#example', components: { 'parent': parentNode } }) </script>
下面示例中,由于子组件使用的是计算属性,所以,子组件的数据无法手动修改
3、更加妥帖的方案是,使用变量储存 prop 的初始值,并使用 watch 来观察 prop 的值的变化。发生变化时,更新变量的值
<div id="example"> <parent></parent> </div> <script src="https://unpkg.com/vue"></script> <script> var childNode = { template: ` <div class="child"> <div> <span>子组件数据</span> <input v-model="temp"> </div> <p>{{temp}}</p> </div> `, props:['childMsg'], data(){ return{ temp:this.childMsg } }, watch:{childMsg(){ this.temp = this.childMsg } } }; var parentNode = { template: ` <div class="parent"> <div> <span>父组件数据</span> <input v-model="msg"> </div> <p>{{msg}}</p> <child :child-msg="msg"></child> </div> `, components: { 'child': childNode }, data(){ return { 'msg':'match' } } }; // 创建根实例 new Vue({ el: '#example', components: { 'parent': parentNode } }) </script>