1.父组件传递数据到子组件
第一种:使用计算属性
原理:
首先,父组件定义在子组件标签上的值会存放到子组件实例的$attr属性上,然后就可以根据这个属性使用计算属性的方式来获取它
其次,定义在子组件标签上的值同时也会放在template的根dom元素上,在本例中也就是div,这也是vue在定义组件的时候必须只有一个根元素的原因
<html>
<head></head>
<body>
<div id='app'>
<h1>父组件</h1>
<component-a :childMsg='msg'></component-a>
</div>
<template id='componentA'>
<div>
<h2>子组件</h2>
</div>
</template>
<body>
<script>
new Vue({
el: '#app',
data: {
msg: '父组件中的数据'
},
component: {
componentA: {
template: '#componentA',
computed: {
getMsg: {
return this.$attr.childMsg
}
}
}
}
})
</script>
</html>
第二种: 在子组件中使用props属性
-
props : 拦截父组件传递的数据变量,取值 Array|Object
Array:数组中使用字符串描述,子组件标签上绑定的对应属性,获取属性值 props: ['property1','property2'......] Object:使用props属性的数据校验功能,完成更加严格的数据获取 (1)value取JS数据类型的包装器=>限制传递过来的数据类型 props: { msg:String } (2)value取值为Object类型=>{key(功能字段): value(功能描述)} props: { msg: { default:() => '默认值', type: String , required: 'false' validator: function(){} } }
当value是Object时的几种功能:
1.default:设置一个默认值,当传递过来的数据类型是undefined会使用该默认值
注意:
(1)null不行,因为采用的是es6的语法:
function show(value='defaultVal'){
console.log(value)
}
show('a')---->打印a
show(null)---->打印null
show() ----->打印defaultVal
(2)如果default返回的数据类型是Array|Object,必须使用函数,避免组件数据共享(类似组件data)
2.required:布尔值,表示是否可以不传递数据
false:可以不传递
true:必须传递
3.validator:取值为Function,提供开发者自定义校验规则
function (value){
...校验
return true or false
}
2.子组件传递数据到父组件$emit
(1)JS中的argument
每一个函数都有一个arguments对象,它包括了函数所要调的参数
通常把arguments当做一个数组使用,但使用instanceof它不是Array
(function (a,b,c){
console.log(arguments)
})(1,2,3)
可以使用arguments来获取当前函数调用的形参
(2)子组件调用父组件的方法来完成参数传递
父组件:
<div class='parent'>
<child @loadChildMsg = 'loadChildMsg'></child>
</div>
new Vue({
name: 'parent',
data: {
msg: ''
},
methods: {
loadChildMsg (childMsg) {
this.msg = childMsg
}
}
})
子组件:
<div class='child'>
<button @click = 'sendMsg'>传递子组件数据到父组件</button>
</div>
new Vue({
name: 'child',
data () {
return {
msg: '子组件的msg'
}
},
methods:{
sendMsg(){
this._events.loadChildMsg [0](this.msg) ---->调用父组件的loadChildMsg
}
}
})
如上代码:重点是子组件的——events
根据之前Vue基础扩展相关,定义在子组件的属性会定义在子组件template的根元素上,但是如果在子组件的方法,类似@loadChildMsg这个方法,在子组件的根元素上并不会出现,而是出现在子组件的_events里面
但是_events是vue实例的隐式参数,不可以直接调用,所以使用下面的方式调用
this.$emit('eventName',data)
(3)上面调用方式的一个常见错误
在子组件中,上面定义的方式为
<child @load-child-msg = 'loadChildMsg'></child>
这种定义方式是正确的,但是如果采用下面加括号的方式:
<child @loadChildMsg = 'loadChildMsg()'></child>
通过子组件来调用父组件的方法是无法将子组件中的变量传递到父组件中的
// =====>子组件调用,传递子组件的msg
this._events.loadChildMsg[0](this.msg)
// ===>通过父组件的loadChildMsg来赋值
loadChildMsg (childMsg){
this.msg = childMsg
}
原理上应该是可以传递过去的,但是这里的msg是undefined
原因
这是因为如果采用括号的方式:Vue会直接调用子组件的无参数方法而不是调用带有参数的方法,但是如果不加括号,这里会将子组件方法的arguments参数传递给真正的loadChildMsg方法,而不是直接调用:
加括号:
loadChildMsg() ------>直接调用,不加参数
不加括号:
loadChildMsg(arguments) ----->会传递当前子组件sendMsg中的argument,也就是将子组件的msg传递给父组件
(4)一些注意事项
1.自定义方法名称时尽量不要使用已知事件,比如click blur等
2.如果想要将定义的方法绑定到子组件根元素上而不是_events,使用.native
<child @loadChildMsg.navice = 'loadChildMsg'></child>
3. 非父子组件的数据传递----中央事件总线bus
场景:
1.new Vue根实例中,存在两个子组件A和B,
2.组件A的info变量需要传递给组件B
3.需要在项目第一次方法时就可以主动将数据进行传递
4.通过事件总线bus实现(3个步骤)
=>通过vm.$root作为中央事件总线实例
=>通过vm.$on完成中央事件总线的自定义事件的绑定
=>通过vm.$emit完成中央事件的自定义事件触发
实现
Vue根实例:
ComponentA:
<button @click='sendInfo'>触发info变量传递</button>----->手动触发参数传递
data () {
return {
info: 'A组件的info变量' ---->A组件的info变量
}
},
methods:{
sendInfo () {
this.$root.$emit('update:info',this.info)--->将A组件的info传递给B组件的arg参数
}
},
mounted () {
}
ComponentB:
data () {
return {
info: '' ---->接收A组件的info变量
}
},
method: {
initInfoEvent() {
this.$root.$on('update:info',function (arg){
/*
$on的作用:相当于在根实例$root的_events上添加了一个监听事件update:info
类似在根实例标签上添加 @update:info = function .......
但是添加不是父组件添加的,而是子组件给根实例添加的
*/
this.info = arg
})
}
},
mounted () {
}
上面的例子中,如果:
在A组件的befoeMount调用this,$root.$emit()方法
在B组件的mounted调用initInfoEvent方法
这样是拿不到数据的,这和组件的生命周期有关
4.补:组件的生命周期
A组件包含B、C两个组件,B里面又包含D、E两个组件
总结:
1.所有组件首先按上图顺序执行生命周期,但是每个组件执行到beforeMount停止
2.然后接着按照上图顺序依次执行mounted以后的生命周期
结论:
每个组件的beforeMount都先于任何一个组件的mounted执行
解决上述问题:
由于在组件A的beforeMount中调用的this.$root.$emit,
但是此时在B组件的initInfoEvent实在mounted执行的
根据上述生命周期,在A的beforeMount时B的mounted肯定还没有执行,导致出错