在 Vue 中,父组件通过 props 向子组件传递数据 (props down),子组件通过 events 向父组件发送消息(events up)。
父子组件
<div id="example">
<parent></parent>
</div>
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
}
})
静态props
组件实例的作用域是孤立的,不能(也不应该)在子组件的模板内直接引用父组件的数据。通过子组件的 props 选项,子组件可以使用父组件的数据。
使用 prop 传递数据包括静态和动态两种形式,首先,子组件要显式地用 props 选项声明它所期望得到的数据。
var childNode = {
template: '<div>{{message}}</div>',
props:['message']
}
静态 prop 通过为子组件在父组件中的占位符添加特性的方式来实现数据的传递。
<div id="example">
<parent></parent>
</div>
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
}
})
命名约定
对于 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'
}
}
};
传递数字
- 使用 v-bind 传递数字,使它的值被当作 JavaScript 表达式计算。
<div id="example">
<my-parent></my-parent>
</div>
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
}
})
初学者常犯的一个错误是使用字面量语法传递数值
var parentNode = {
template: `
<div class="parent">
<my-child my-message="1"></my-child>
</div>`,
components: {
'myChild': childNode
}
}
因为它是一个字面 prop,它的值是字符串 “1” 而不是 number。
- 也可以使用动态 props,在 data 属性中设置对应的数字。
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 会抛出警告(开发版本下)。prop 会在组件实例创建之前进行校验,所以在 default 或 validator 函数里,诸如 data,computed 或 methods 等实例属性还无法使用。
检测子组件的 message 是不是数字:
<div id="example">
<parent></parent>
</div>
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
}
})
传入数字时无警告提示。如果改为字符串 “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
}
}
};
单向数据流
props 是单向绑定的:父组件的属性变化时,会传递给子组件,但不会反过来。这样可以防止子组件意外修改父组件的状态。
另外,每当父组件更新时,子组件的所有 prop 都会更新为最新值。这意味着不应该在子组件内部改变 prop。在子组件内部修改 prop,Vue 会在控制台抛出警告。
<div id="example">
<parent></parent>
</div>
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
}
})
父组件数据变化时,子组件数据会跟着变化;而子组件数据变化时,父组件数据不变,并在控制台抛出警告。
修改 prop 数据
以下两种情况下,需要修改 prop 数据:
- prop 作为初始值传入后,子组件想将其作为局部数据来使用
- prop 作为初始值传入后,子组件将其处理成其他数据输出
注意:JavaScript 中对象和数组是引用类型,指向同一个内存空间,如果 prop 是对象或数组,在子组件内部修改它会影响父组件的状态。所以,正确的修改方式是:
- 定义一个局部变量,并用 prop 的值初始化它
props: ['initialCounter'],
data(){
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>
- 定义一个计算属性,处理 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>
`,//修改<input>里的内容时,<p>里的内容不变化,但是父变<input>和<p>都变
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>
使用计算属性的问题是,只能显示值,不能设置值,所以子组件的数据无法手动修改。
- 更妥贴的方式是,使用变量存储 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>