vue 父子组件、兄弟组件传值
参考文章:
Vue2.x子同级组件之间数据交互
(一)父子组件之间的通信
父组件给子组件传参,子组件通过props拿到参数
父组件:
<template> <div> <h1>父组件</h1>
<!-- 引入子组件 --> <child :sendMsg="fatherMsg"></child> </div> </template> <script> import child from '@/components/child' export default { name: 'father', components: { child }, data() { return { fatherMsg: '嗨,儿子' // 传递给子组件的值 } } } </script>
子组件:通过props拿到父组件传递过来的值
<template> <div> <h1>子组件</h1> <span>获取父组件传值:{{sendMsg}}</span> </div> </template> <script> export default { name: 'child', data() { return { } }, props: ['sendMsg'] // 拿到父组件绑定到sendMsg的值,然后在子组件下显示出来 } </script>
子组件给父组件传值:通过触发事件传递值,子组件可以使用 $emit 触发父组件的自定义事件。
关键字:$emit()
以上面的示例代码作为基础修改,子组件:
<template> <div> <h1>子组件</h1> <span>获取父组件传值:{{sendMsg}}</span><hr> <button @click="sendToFather">子组件给父组件传值</button> </div> </template> <script> export default { name: 'child', data() { return { childMsg: '这是来自子组件的数据' } }, props: ['sendMsg'], methods: { sendToFather: function() { this.$emit('getChildValue', this.childMsg); // 参数1 getChildValue作为中间状态,参数2 this.childMsg即为传递给父组件的数据 } } } </script>
父组件:
<template> <div> <h1>父组件</h1> <!-- 引入子组件 定义一个on的方法监听子组件的状态,然后通过getChild方法获取子组件传递的数据--> <child :sendMsg="fatherMsg" v-on:getChildValue="getChild"></child> <span>这是来自子组件的数据:{{childValue}}</span> </div> </template> <script> import child from '@/components/child' export default { name: 'father', components: { child }, data() { return { fatherMsg: '嗨,儿子', childValue: '' } }, methods: { getChild: function(data) { // 此时的参数data为子组件传递的值,即this.$emit()的第二个参数 this.childValue = data; } } } </script>
(二)同级组件传递数据
对于同级组件传值用的较多的情况,推荐直接使用vuex进行状态管理会比较方便。
补充:面试被问到同级组件传递参数,平时很少去用这个,没答上来(尴尬),回来百度了下,原来就是通过一个中间桥接的方式进行传递(遭不住,之前看到过,没引起重视)
其原理是先建立一个中间事件总线center.js,放在tools文件夹下,如下:
import Vue from 'vue' export default new Vue()
center.js中我们只创建了一个新的Vue实例,以后它就承担起了组件之间通信的桥梁了,也就是中央事件总线
然后创建第一个子组件first.vue:
<template> <div class="first-vue-box"> <p>this is firstChild vue</p> <button @click="sendMsg">发送</button> </div> </template> <script> import bridge from '../tools/center' export default { data () { return {} }, methods: { sendMsg () { bridge.$emit('firstChildMsg', 'this is firstChild Msg') } } } </script> <style lang="scss" scoped> .first-vue-box { border: 1px solid blue; } </style>
这里先引入事件总线,通过事件总线点击按钮后将first.vue的信息通过事件firstChildMsg的形式发布出去了(我个人的理解是相当于通过事件总线,将first.vue的firstChildMsg这个事件暴露出去),用法跟子组件向父组件传参的模式一样
然后再创建第二个组件second.vue:
<template> <div class="second-child"> <h4>this is second child vue</h4> <p>从first.vue获取同级组件传递过来的信息:{{message}}</p> </div> </template> <script> import bridge from '../tools/center' export default { data () { return { message: '默认值' } }, mounted () { let _this = this bridge.$on('firstChildMsg', function (msg) { _this.message = msg }) } } </script>
在second.vue中再引入事件总线,然后通过$on(functionName, callback)监听first.vue暴露出来的firstChildMsg事件,通过回调函数获取first.vue传递出来的值(我个人是这么理解的)
引入两个子组件:
<template> <div class="detail-div"> <h3>首页详情</h3> <first-child></first-child> <second-child></second-child> </div> </template> <script> import firstChild from './first' import secondChild from './second' export default { data () { return {} }, components: { firstChild, secondChild }, mounted () { // console.log('详情页面...') } } </script>
第二个子组件通过中央事件总线bridge,监听事件first.vue发布的事件firstChildMsg,获取到子组件first.vue发送过来的信息,如下图所示:
点击发送按钮后second.vue获取到first.vue传递过来的值:
组件间其他通信方式补充(2021-08-13):
(三)通过vuex
vuex是vue的一个状态管理插件,可以实现单向数据流,在全局得到一个state对象,设置成功之后,可以在任意的组件中可以通过this.$store.state.name获取存储在store对象中的值,也可以通过vuex的mutation同步、action异步方法修改状态值,修改之后的,在任意组件再通过this.$store.state.name获取到的值就行修改之后的值,实现组件之间的通信(这里只是说明一种通信方式,具体的同步修改状态方法等需要自己去看vuex的文档操作即可)
运用场景:组件多层嵌套,且存在中途修改传递的值的情况
(四)通过$attrs和$listeners
如果只是组件层层嵌套传递参数,不做其他处理这样的,还可以通过vue2.4中提供的$attrs和$listeners来实现,这两方法的作用如下:
$attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件。通常配合 inheritAttrs 选项一起使用。
$listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件
这里我创建了5个vue文件,然后层层嵌套,代码和显示效果如下:
<template> <div> <h3>顶层的父组件</h3> <p>父组件传入的字段有-->boo:{{boo}},coo:{{coo}},doo:{{doo}},eoo:{{eoo}},foo:{{foo}}</p> <child1 :boo="boo" :coo="coo" :doo="doo" :eoo="eoo" :foo="foo" /> </div> </template> <script> const child1 = () => import('./child1') export default { components: { child1 }, data () { return { boo: 'start', coo: '120', doo: '130', eoo: '140', foo: 'end' } } } </script>
<template> <div> <h3>第一级子元素child1</h3> <p>child1通过props属性获取boo的值:{{boo}}</p> <p>child1的$attrs的值{{$attrs}}</p> <child2 v-bind="$attrs" /> </div> </template> <script> const child2 = () => import('./child2') export default { components: { child2 }, props: { boo: { type: String, default: '' } }, inheritAttrs: false, created () { console.log(this.$attrs) } } </script>
<template> <div> <h3>第二级子元素child2</h3> <p>child2通过props属性获取doo的值:{{doo}}</p> <p>child2中的$attrs的值{{$attrs}}</p> <child3 v-bind="$attrs" /> </div> </template> <script> const child3 = () => import('./child3') export default { components: { child3 }, props: { doo: { type: String, default: '' } }, inheritAttrs: false, created () { console.log(this.$attrs) } } </script>
<template> <div> <h3>第三级子元素child3</h3> <p>child3通过props属性获取doo的值:{{coo}}</p> <p>child3中的$attrs的值{{$attrs}}</p> <child4 v-bind="$attrs" /> </div> </template> <script> const child4 = () => import('./child4') export default { components: { child4 }, props: { coo: { type: String, default: '' } }, inheritAttrs: false, created () { console.log(this.$attrs) } } </script>
<template> <div> <h3>第四级子元素child4</h3> <p>eoo的值:{{eoo}}</p> <p>child2中的$attrs的值{{$attrs}}</p> </div> </template> <script> export default { props: { eoo: { type: String, default: '' } }, inheritAttrs: false, created () { console.log(this.$attrs) } } </script>
各层组件的$attrs结果如下:
由上面的显示结果可以看出,$attrs是一个对象,可以得到了上一级绑定的所有非prop属性的值,如果下一级还有需要,再接着通过v-bind将$attrs传递下去即可,$listeners的使用待查资料
(五)通过provide和inject(提供/注入的方式跨级通信)
运用场景:在子孙组件上通过inject注入,拿到祖父级组件provide提供的变量或对象值,实现跨级通信
利用上面的代码,我再新建一个vue文件child5.vue,作为第五级子组件,嵌入到child4.vue中,然后在顶层的index.vue中通过provide提供需要传递的值,然后在第五级子组件child5.vue中通过inject获取注入的值,代码如下:
// 这里是最顶层的index.vue,代码就简写了 ... provide: { name: 'provide-inject组件通信方式' },
// 这里是最底层的子组件,位于第五层 <template> <div> <h3>第五级子组件child5</h3> <p>这里验证provide、inject组件通信模式</p> <p>获取越级顶层祖先组件的值:{{name}}</p> </div> </template> <script> export default { inject: ['name'], mounted () { console.log(this.name) } } </script>
展示如下:
如上所示,我们在新建第五级子组件中,拿到顶层组件index.vue中provide提供的值,实现了跨级通信,但是会有个问题,当我们在父组件中修改提供的name值的时候,发现在第五级子组件的值没有发生变化,就是他们之间不是一个响应式的
要实现跨级响应式通信,可以通过vue2.6的api Vue.observeable()来进行优化
下面是我根据大佬的代码写的验证代码和展示效果
上层的祖级组件:
provide () { this.name = Vue.observable({ // 通过vue2.6新提供的Vue.observable(),实现祖级组件提供的值发生改变,孙祖组件可以立马更新 Vue.observable() color: 'hi, bob' // 默认传值为'hi, bob',是一串字符串 }) return { name: this.name } }
methods中的改变传入值方法,这里我传入0-10之间的随机数
子孙级组件:
inject: { name: { default: () => ({}) } }
进入页面时的原始值:
点击顶层祖级组件按钮事件之后的值:
(六)$parent/$childer与ref
ref:在子组件上使用,可以调用子组件的方法和访问子组件的数据
$parent/$children:通过$parent访问父组件/通过$children访问子组件实例
这两种方式都是得到组件实例对象,然后通过实例对象的方式去调用组件中的方法和访问数据
总结
常见使用场景可以分为三类:
- 父子通信: 父向子传递数据是通过 props,子向父是通过 events(
$emit
);通过父链 / 子链也可以通信($parent
/$children
);ref 也可以访问组件实例;provide / inject API;$attrs/$listeners
- 兄弟通信: Bus;Vuex
- 跨级通信: Bus;Vuex;provide / inject API、
$attrs/$listeners
这里很感谢原作者大大 浪里行舟,之前的时候只了解vuex、父子组件、事件总线以及父组件通过ref属性调用子组件这几种方式,其他的都没看过了解过,看完文章再手动敲代码测一遍,增加了好些不知道的知识,多谢大佬了(^_^)