相较于props,用自定义事件的方法实现子组件给父组件传参更合理和方便,但props也确实可以实现 ‘子传父’。
例子中一共有三个组件 App组件,Helloworld组件,ChildrenVue组件。app组件管理后两个组件
APP组件中的内容
//App.vue
...
<HelloWorld :msg="msg"></HelloWorld>
<ChildrenVue></ChildrenVue>
...
import HelloWorld from './components/HelloWorld.vue'
import ChildrenVue from './components/ChildrenVue.vue';
export default {
name: 'App',
data(){
return {
msg:'Hollo,I`m a Vue.js app.'
}
},
components: {
HelloWorld,
ChildrenVue
}
}
...
HelloWorld组件中的内容
//HelloWorld.vue
...
<div class="hello">
<h1>{{msg}}</h1>
</div>
...
export default {
name: 'HelloWorld',
props: {
msg: {type:String, default:'Send message to me,OK?'},
}
。}
...
ChildrenVue组件中的内容
...
<div id="children">
</div>
...
export default {
name:'ChildrenVue',
}
用Vue脚手架工具生成的页面,手动添加了一个子组件,并更改了默认msg传参的内容。
props传参同props父传子参数类似,具体实现原理是父组件定义函数,并将函数传递给子组件,在子组件中调用,并将自己的数据作为参数传递给父组件的函数。大体可分为四步。
第一步,在子组件 标签上 上使用v-bind(简写为 :)绑定传递的参数名以及传递的处理函数;
第二步,在子组件的 script 中 添加 props 属性 ,并将 参数名 接收;
第三步,在子组件中调用其他函数来触发用 props接收的参数(也就是那个函数)
第四步,子组件的参数被传递出去
用到APP和Children两个组件
APP组件中的内容
//App.vue
...
<HelloWorld :msg="msg"></HelloWorld>
<ChildrenVue :getmsg="getmsg"></ChildrenVue>
...
import HelloWorld from './components/HelloWorld.vue'
import ChildrenVue from './components/ChildrenVue.vue';
export default {
name: 'App',
data(){
return {
msg:'Hollo,I`m a Vue.js app.'
},
methods:{
getmsg(msg){
console.log(msg);
}
},
components: {
HelloWorld,
ChildrenVue
}
}
...
ChildrenVue组件中的内容
...
<div id="children">
<button @click="sendmsg">sendmsg</button>
</div>
...
export default {
name:'ChildrenVue',
props:[ 'getmsg' ],
data(){
return {
msg:'I`m children'
}
},
methods: {
sendmsg(){
return this.getmsg(this.msg)
}
},
}
实现效果是控制台输出 I`m children 。
自定义事件有两种方法触发,先说第一种。
其主要实现也是靠函数参数传递的方法将数据带给父组件,具体分三步:
第一步,在子组件 标签上 上使用v-on(简写为 @)绑定自定义的 事件名 以及对应的处理函数,可以类比原生事件 @click=“handle(e)”;
第二步,在子组件中调用其他函数,使用 this.$emit(‘事件名’,参数) 的方法触发事件;
第三步,子组件的参数被传递出去
这次用app和helloworld两个组件
APP组件中的内容
//App.vue
...
<HelloWorld :msg="msg" @addnumber='addnumber'></HelloWorld>
<ChildrenVue :getmsg="getmsg"></ChildrenVue>
...
import HelloWorld from './components/HelloWorld.vue'
import ChildrenVue from './components/ChildrenVue.vue';
export default {
name: 'App',
data(){
return {
msg:'Hollo,I`m a Vue.js app.'
},
methods:{
getmsg(msg){
console.log(msg);
},
addnumber(num){
console.log(num+10);
}
},
components: {
HelloWorld,
ChildrenVue
}
}
...
HelloWorld组件中的内容
//HelloWorld.vue
...
<div class="hello">
<h1>{{msg}}</h1>
<button @click="sendnumber">addnumber</button>
</div>
...
export default {
name: 'HelloWorld',
data(){
return {
num:30
}
},
props: {
msg: {type:String, default:'Send message to me,OK?'},
},
methods:{
sendnumber(){
this.$emit('addnumber',this.num)
}
}
}
...
最终效果是控制台输出 40
到目前为止,props和自定义事件好像差别并不大,自定义事件虽然少写了写参数,且子组件看着更简洁,并没有什么明显的过人之处,现在看第二种方法:
APP组件中的内容
//App.vue
...
<HelloWorld :msg="msg" ref="Helloworld"></HelloWorld>
<ChildrenVue :getmsg="getmsg"></ChildrenVue>
...
import HelloWorld from './components/HelloWorld.vue'
import ChildrenVue from './components/ChildrenVue.vue';
export default {
name: 'App',
data(){
return {
msg:'Hollo,I`m a Vue.js app.'
},
methods:{
getmsg(msg){
console.log(msg);
},
addnumber(num){
console.log(num+10);
}
},
components: {
HelloWorld,
ChildrenVue
},
mounted(){
this.$refs.Helloworld.$on('addnumber', this.addnumber)
}
}
...
HelloWorld组件不需要任何变化
主要变动有两处,第一处 是子组件标签中 使用ref属性;第二处 是不在methods或者watch中 定义处理函数,而是在生命周期函数中处理。
这样的好处是更加灵活,自定义事件可以添加其他异步操作,且对子组件绑定多个自定义事件时,也无需考虑太多的逻辑,不断地在 生命周期函数中 使用 $on 来处理即可。
由上可以看出 $on 接收两个参数,$on(自定义事件名,函数) 而函数可以用箭头函数来简写操作,优化后的代码如下:
APP组件中的内容
//App.vue
...
<HelloWorld :msg="msg" ref="Helloworld"></HelloWorld>
<ChildrenVue :getmsg="getmsg"></ChildrenVue>
...
import HelloWorld from './components/HelloWorld.vue'
import ChildrenVue from './components/ChildrenVue.vue';
export default {
name: 'App',
data(){
return {
msg:'Hollo,I`m a Vue.js app.'
},
methods:{
getmsg(msg){
console.log(msg);
}
},
components: {
HelloWorld,
ChildrenVue
},
mounted(){
this.$refs.Helloworld.$on('addnumber', num =>{ console.log(num+10) })
}
}
...
但需要注意, $on 中定义的函数this是指向 调用该事件的组件,也就是子组件,若我想在html页面中打印内容,则需这样写:
APP组件中的内容
//App.vue
...
<HelloWorld :msg="msg" ref="Helloworld"></HelloWorld>
<p>子组件的数计算后是:{{addnumber}}</p>
<ChildrenVue :getmsg="getmsg"></ChildrenVue>
...
import HelloWorld from './components/HelloWorld.vue'
import ChildrenVue from './components/ChildrenVue.vue';
export default {
name: 'App',
data(){
return {
msg:'Hollo,I`m a Vue.js app.',
addnumber: undefined
},
methods:{
getmsg(msg){
console.log(msg);
}
},
components: {
HelloWorld,
ChildrenVue
},
mounted(){
this.$refs.Helloworld.$on('addnumber', num =>{
console.log(num+10);
this.addnumber = num+10; })
}
/*错误写法
mounted(){
this.$refs.Helloworld.$on('addnumber', function(num){
console.log(num+10)
this.addnumber = num+10 })
}
}*/
}
...
使用箭头函数可以避免this指向 “错误” ,函数内部更改的值为data里的数据。
自定义事件也有 $once 可以触发一次自定义事件。
使用自定义事件要注意区分开原生事件,可以归纳为两句话:
原生DOM节点用自定义事件没有意义,
自定义节点只能用自定义事件
例如 在自定义节点用
...
<HelloWorld :msg="msg" @click='addnumber'></HelloWorld>
...
意思是一个事件名为click的自定义事件,若想用原生事件,需要这样写
...
<HelloWorld :msg="msg" @click.native='addnumber'></HelloWorld>
...
在子组件向父组件传参时,优先考虑的还是自定义事件的方法(当然事件总线,vuex更优先,总之不要props)。
在没有复杂逻辑时,可以直接用 @自定义事件名=‘处理函数’
的方法处理,而在子组件,可以用 @click=‘$emit(事件名, 要传的参数)’
来快速相应(正常开发没人这么用吧)。