vue 父子组件、兄弟组件传值

参考文章:
Vue2.x子同级组件之间数据交互

vue组件间通信六种方式(完整版)

(一)父子组件之间的通信

父组件给子组件传参,子组件通过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之间的随机数
  changeProvide () {
      this.name.color = Math.floor(Math.random() * 10) + 1
  }
 

子孙级组件:

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属性调用子组件这几种方式,其他的都没看过了解过,看完文章再手动敲代码测一遍,增加了好些不知道的知识,多谢大佬了(^_^)

原文地址:https://www.cnblogs.com/secretAngel/p/9705809.html