Vue父子组件传值&自定义事件方式
作者:白桃味稠鱼烧
Vue 父子组件传值&自定义事件
因为vue 的数据是单向流动的,这是为了避免数据污染。
在官方文档中也说到:所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。
这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
大致归纳一下:父传子--传值、子传父--传事件
父传子:父组件可以传递任何类型的数据给子组件
如果传递的数据是对象格式的,那么在子组件 内部监听 watch 的时候,需要使用深度监听,也就是添加 deep : true ,也就是下面的子组件的监听方式,如果是别的格式的,例如,字符串、数字、布尔值、 数组格式等,那就是普通监听就好了
父组件代码
在父组件中 通过 v-bind 的缩写形式 :listData='listData' 绑定了data 内部的数据,第一个 listData 只是一个名字,为了方便辨认,所以写的相同。 第二个 listData 则是 data 内部的数据
<template> <div> <h3>我是father</h3> <Children :listData='listData' :xxx='xxx' :listObj='listObj'></Children> //子组件传递了一个数组、一个字符串、一个对象 </div> </template> <script> import Children from './children' //引入子组件 export default { data () { return { xxx:'123', listData: [{ id: 1, name: "TCL彩电", price: 1000, num: 1, img: "../../../assets/a.jpg" }], listObj: { name: "aaa", age: 18 } } }, components : { Children //注册子组件 }, } </script>
子组件代码
1、通过 props 接收父组件传递过来的数据,规范要求写出数据的类型以及默认值,如果数据是数组或对象形式的,需要使用函数返回,不然控制台会报错。
2、props 接收数据之后,需要使用数据,这个时候需要用到 watch 监听器。对象监听需要用到 deep 深度监听,如果需要组件第一次进来之后就开始监听数据,那么需要 添加 immediate: true
<template> <div> <h3>我是children</h3> </div> </template> <script> export default { props: { listData: { type: Array, default: () => [] }, xxx:{ type:String, default : '' }, listObj:{ type: Object, default: () => {} } }, watch: { listData:{ handler(n,o) { console.log(n,o) } }, xxx:{ handler(n) { console.log(n) } }, listObj:{ handler(n,o) { console.log(n,o) }, deep: true, immediate: true, }, } }; </script>
子传父--传事件:子组件传递数据给父组件时存在三种方式,但是都是通过事件传递
1、父组件传递 函数类型的props 给子组件,实现子组件向父组件传递数据
在父组件中引入子组件,向子组件中 通过 v-bind( 简写为 : ) 绑定一个 test 属性 ,该 test 属性对应的值则是 methods 中定义的方法。
<template> <div id="app"> <School :test="test"/> </div> </template> methods: { test(val) { console.log(val,'这是子组件传递过来的数据') }, },
定义子组件,以及子组件事件
<template> <div> <p class="demo" @click="goto">School组件</p> </div> </template>
在子组件中接收该 test 属性,定义数据,定义组件方法。其实props 可以直接写成一个数组,不去定义类型,默认值以及是否必传,但是推荐还是写全一点,这样编译的时候会校验,提高代码质量
export default ({ // props:['test'], props: { test: { type: Function, default: () => {}, required: true, } }, data() { return { msg:'子组件数据' } }, methods: { goto() { this.test(this.msg) } }, })
点击触发 goto 事件,找到当前 props 中接收的 test 函数 ( props 接收的参数,都被Vue 底层处理过之后放在了 当前组件实例对象上,所以可以直接通过 this.xxx 拿到 )
控制台上打印了子组件数据。可以看到子组件传递的数据被打印了,表示父组件中绑定的 test 事件被执行了
2、通过 v-on( @ ) 与 $emit 实现子组件向父组件传递数据
App 组件中引入 School 子组件,且绑定 自定义事件 test。
<School @test="test"/> test(val) { console.log(val,'这是子组件传递过来的数据') },
子组件模板、数据、样式不变,只是 goto 方法内部逻辑变更
methods: { goto() { this.$emit('test',this.msg) } },
点击触发 goto 事件,通过 $emit 触发 test 事件,根据名称找到 父组件中的 test 属性对应的方法,执行该方法。结果与 props 传递函数参数一致
3、通过 ref 以及 $on、$emit 三个 api 实现 父组件通过 自定义事件接收子组件参数
$on
:监听当前实例上的自定义事件。事件可以由 vm.$emit 触发。回调函数会接收所有传入事件触发函数的额外参数。$emit
:触发当前实例上的事件。附加参数都会传给监听器回调。
App 组件中引入 Schoo 组件,且给 School 子组件添加了 ref 属性,定义 test 函数
<School ref='student'/> test(val) { console.log(val,'这是子组件传递过来的数据') },
如果想使用这个方法去获取子组件数据,就需要用到 $on() 这个方法。现在假设,当父组件挂载时,我就要获取到子组件的值,我就应该在 父组件 的 mounted 生命周期中 使用 this.$refs.xxx来获取当前组件的实例对象,至于 $on() 这个方法,则是 挂载到 Vue 实例对象的原型上的,所以 组件实例对象 和 Vue 实例对象 都能使用 $on() 。
在这里就是 通过 $on 注册或者叫创建了一个 qwe 的自定义事件,且该自定义事件的回调函数是写在 methods 中的 getname
mounted() { this.$refs.student.$on('qwe', this.test) }
父组件的工作已经完了,现在该看看子组件了。子组件更简单了,和上面 第二种方法一样,通过 $emit() 这个方法来触发父组件定义的 qwe 方法,且将子组件 数据传递出去。
goto() { this.$emit('test',this.msg) }
当我点击 School 组件时,执行 goto 方法,通过 $emit 触发父组件自定义的 qwe 方法,且将参数传递给父组件。
父组件通过 $on 监听 qwe 方法,发现被触发了,执行其回调函数 this.test,且 $emit 传递的参数,都会当做形参传递到回调函数中
$on 和 v-on 的区别
这么一看哈,其实我在子组件上使用 v-on( @ ) 和我使用 $on 做到的事情是一样的啊,那为啥还要来个 $on 这个玩意。
- 第一点:$on 只能监听当前实例上的自定义事件,而 v-on 用在普通元素上时,只能监听原生DOM事件。用在自定义元素组件上时,也可以监听子组件触发的自定义事件。
- 第二点:$on 更加灵活,如果我想要我的自定义事件异步绑定,通过 v-on 是无法做到的,因为 v-on 指令在 模板编译的时候,就被Vue 底层处理过了,在渲染的时候直接就会绑定事件,但是 $on 的自由度更高。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。