在进入这个话题之前,首先我们先来想一下在vue里,如何写一个父子组件。为了简单起见,下面的代码我都没用脚手架来构建项目,直接在html文件里引入vue.js来作为例子。父子组件的写法如下:
<div id="app">
<parent></parent>
</div>
<script src="node_modules/vue/dist/vue.js"></script>
<script>
let childNode = {
template: `<div>childNode</div>`
}
let parentNode = {
template: `
<div>
<child></child>
</div>
`,
components: {
child: childNode
}
}
new Vue({
el: '#app',
components: {
parent: parentNode
}
})
</script>
在这个代码里,template表示的是模板,components表示的是组件。子组件先要在父组件里显示就要通过components把组件对应的模板引擎(childNode对象)给挂载到父组件的components上去。然后子组件就会在父组件上得以显示。
在父子组件中,如果想要父组件的变量传递到子组件中,则需要通过props属性来进行传递。props有静态的也有动态的,下面来介绍静态的写法:
<script>
let childNode = {
template: `<div>{{message}}</div>`,
props: ['message']
}
let parentNode = {
template: `
<div>
<child message="child"></child>
</div>
`,
components: {
child: childNode
}
}
new Vue({
el: '#app',
components: {
parent: parentNode
}
})
</script>
在这里,子组件通过在props里写入想要获取的父组件的某个属性值,在父组件里,通过父组件在子组件里的占位符添加属性的方式来传值。在这里有一个命名规范,在子组件里,如果props里的参数由多个词组成则应该用驼峰命名的写法;而在父组件中则用横线做分隔符的写法。如:
<script>
let childNode = {
template: `<div>{{myMessage}}</div>`,
props: ['myMessage']
}
let parentNode = {
template: `
<div>
<child my-message="child"></child>
</div>
`,
components: {
child: childNode
}
}
new Vue({
el: '#app',
components: {
parent: parentNode
}
})
</script>
而动态props的用法是将占位符进行绑定,将父组件的数据绑定到子组件的props中,可以实现父组件的数据发生改变的时候子组件的数据也会发生改变的动态效果。如:
<script>
let childNode = {
template: `<div>{{myMessage}}</div>`,
props: ['myMessage']
}
let parentNode = {
template: `
<div>
<input type="text" v-model="myData">
<div>
<child :my-message="child"></child>
</div>
</div>
`,
components: {
child: childNode
},
data(){
return {
child: '',
myData: ''
}
},
watch: {
myData(newValue, oldValue){
this.child = newValue
}
}
}
new Vue({
el: '#app',
components: {
parent: parentNode
}
})
</script>
在这里,我通过利用v-model和watch来实现输入框发生变化的时候父组件数据动态修改后子组件的数据跟着改变。为什么这里不用computed而用watch呢?这里其实也涉及到watch和computed的区别,在vue里,这两个方法都能监听数据的变化,不同的是computed是只能读取不能写入,而watch可以读取也可以写入。当父组件的myData这个值发生改变的时候就将myData的值不断赋值给child,由于对占位符进行了绑定,所以子组件能够接收到父组件的改变。
动态props和静态props除了上面的区别以外还有一个区别,就是传递参数的时候数据类型发生变化。如下面的静态props里
<script>
let childNode = {
template: `<div>{{myMessage}}的类型是{{type}}</div>`,
props: ['myMessage'],
computed: {
type() {
return typeof this.myMessage
}
}
}
let parentNode = {
template: `
<div>
<div>
<child my-message="1"></child>
</div>
</div>
`,
components: {
child: childNode
}
}
new Vue({
el: '#app',
components: {
parent: parentNode
}
})
</script>
我们传的1在子组件里却是string类型,而如果是加入了绑定变成动态props:
<div>
<child :my-message="1"></child>
</div>
则会变成nunber,这种情况除了在数字类型发生以外,其他类型也会发生,需要注意。
在props里,我们可以验证父组件传过来的参数是否有问题,下面的例子主要是验证父组件传过来的数据是否是Number类型,如果不是的话则会在控制台里看到报错。在用这个方法的时候不要引用vue.min.js而要引用vue.js,因为vue.min.js会省略掉相应的报错提示,不利于开发的时候查看:
<script>
let childNode = {
template: `<div>{{myMessage}}</div>`,
props: {
myMessage: Number
}
}
let parentNode = {
template: `
<div>
<input type="text" v-model="myData">
<div>
<child :my-message="child"></child>
</div>
</div>
`,
components: {
child: childNode
},
data() {
return {
child: 123,
myData: ''
}
},
watch: {
myData(newValue, oldValue) {
if (/^[0-9]*$/.test(newValue)) {
this.child = parseInt(newValue)
} else {
this.child = '输入的数据不是number类型'
}
}
}
}
new Vue({
el: '#app',
components: {
parent: parentNode
}
})
</script>
可以看到我是在props里添加了一个对象,该对象里的myMessage属性里有属性值Number,表示验证数字类型,当myMessage的值不是数字类型的时候就会报错。除了Number的验证规则以外还有String、Boolean、Function、Object、Array和Symbol等验证规则,同时也可以写成这样来验证多种类型:
props: {
myMessage: [Number, String]
}
或者可以通过一个自定义一个工厂函数来进行匹配,如:
props: {
myMessage: {
validator: function (value) {
return value > 10
}
}
}
当然,在props对象里面,还有另外三个属性,一个是default属性,表示的是父组件传入值的时候子组件默认的值,另一个是require,表示的是该值是否要传入,而type则表示的是验证规则。如:
<script>
let childNode = {
template: `<div>{{myMessage}}</div>`,
props: {
myMessage: {
type: Number,
// required: true,
default: 321
}
}
};
let parentNode = {
template: `
<div>
<div>
<child></child>
</div>
</div>
`,
components: {
child: childNode
},
data() {
return {
child: 123
}
}
};
new Vue({
el: '#app',
components: {
parent: parentNode
}
})
</script>
一般来说,当props里的require表示为true的时候,则父组件要加上占位符,因为父组件有传值过来,所以default的值被覆盖了,如:
<script>
let childNode = {
template: `<div>{{myMessage}}</div>`,
props: {
myMessage: {
type: Number,
required: true,
default: 321
}
}
};
let parentNode = {
template: `
<div>
<div>
<child :my-message="child"></child>
</div>
</div>
`,
components: {
child: childNode
},
data() {
return {
child: 123
}
}
};
new Vue({
el: '#app',
components: {
parent: parentNode
}
})
</script>
由于props是单向数据流,所以父组件传过来的数据子组件修改了也不会修改到父组件的数据,而父组件一修改了则会修改到子组件的数据。所以想要反过来将子组件的数据传到给父组件则要换一种方法。用的比较多的是自定义一个事件,如:
<script>
let childNode = {
template: `
<div>
<button @click="toParent">子传父</button>
</div>
`,
methods: {
toParent() {
this.$emit('myParent', 'hello')
}
}
};
let parentNode = {
template: `
<child @myParent="change" :message="message"></child>
`,
components: {
'child': childNode
},
methods: {
change(data) {
console.log(data)
}
}
};
new Vue({
el: '#app',
components: {
parent: parentNode
}
})
</script>
在这里,子组件通过比如点击事件来触发一个自定义事件,该事件里有this.$emit的处理函数,第一个参数表示的是父组件里负责监听的函数名,第二个参数表示的传递的数值。在父组件里,通过在子组件占位符绑定一个子组件this.$emit定义的方法名即可监听得到传入的参数。