VUE组件通信传值详细总结(`悄悄话’我知道2.0 与 3.0)

7,259 阅读4分钟

引言

vue的两大特点就是响应式原理组件系统,今天我们就来看一下关于组件系统中各级组件是如何传递数据的。

之后的演示都在如下图的关系网中进行: Father对于SonDaughter来说为父组件。 Son对于Father为子组件,但是对于 GrandSonBySonGrandDaughterBySon为父组件。 Daughter对于Father为子组件,但是对于 GrandSonByDau为父组件。 同时,DaughterSon有同一个父组件 Father因此这两个组件互为兄弟组件

一、使用prop(父传子)和$emit(子传父)

父组件通过子组件暴露的prop的sonValue、dauValue向子组件传值。

//父组件
<template>
  <div class="father">
    <son v-bind:sonValue="son"></son>
    <daughter v-bind:dauValue="daughter"></daughter>
  </div>
</template>
<script>
import Daughter from '@/views/Daughter.vue'
import Son from '@/views/Son.vue'
export default {
  components: {
    Daughter,
    Son
  },
  data () {
    return {
       son:'son',
       daughter:'daughter'
    }
  }
}
</script>

子组件通过内部定义的props接收到父组件的值,每当父组件的值变化的时候,子组件内部的值就会发生变化,可以使用this.[props]来进行引用。

2.0代码

// 子组件
<template>
  <div class="father">
    {{sonValue}}  <!-- son -->
  </div>
</template>
<script>
export default {
  props:{
    sonValue:{
      type:String,
      default:""
    }
  },
  data () {
    return {

    }
  },
  mounted(){
    console.log(this.sonValue) // 父组件输入的值'son'
  }
}
</script>

3.0 代码

import { defineProps } from 'vue'
const props = defineProps({
     sonValue:{
      type:String,
      default:""
    }
})

子组件向父组件传值其实就是触发事件然后通过事件传参返回给父组件。

2.0代码

// 子组件中触发
this.$emit('事件名','值')

3.0 代码

import { defineEmits } from 'vue
const emits = defineEmits(['事件名'])
// 子组件中触发
emist('事件名','')
// 父组件中将事件绑定回父实例的函数上,第一个参数为子组件的传值。
<son v-bind:sonValue="son" @changeFather="change"></son>

methods:{
    change(value){
       //value是子组件的传过来的值
       this.fromSon = value
    }
}

注意:Vue 单向数据流 的原因所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定 父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。额外的,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。

  // 子组件son的修改
  data () {
    return {
       //初始化一个子组件内部的值,将传入的值赋值。
       realSonValue:this.sonValue
    }
  },

Vue3.0 中的对于v-model 的修改,将原有的v-model 对于字组件的默认值从value 改成了modelValue 以及可以给v-model 后面增加动态的变量v-model:变量 来代替了以往的2.0 的.sync。

二、vm.$ref.[child]vm.$parent(父子组件之间的显示调用)

2.0

vm.$ref.[child]在父组件中引入子组件并且设置子组件的ref值。ref="son"

// 父组件
<template>
  <div class="father">
    <son v-bind:sonValue="son" @changeFather="change" ref="son"></son>
    {{fromSon}}
    <daughter v-bind:dauValue="daughter"></daughter>
  </div>
</template>
// 父组件都可以通过引用'ref'的方式来获得。
mounted(){
    console.log(this.$refs.son.innerValue) // '$refs.son'
    this.$refs.son.innerFun() // 'I am Son'
}

子组件内部定义的一个值、或者方法。

data () {
    return {
       // 初始化一个子组件
       innerValue:'$refs.son'
    }
},
methods:{
    innerFun(){
      console.log('I am Son')
    }
},

vm.$parent在子组件中引入父组件实例

// 父组件
data () {
    return {
       innerFather:'vm.$parent'
    }
},
// 子组件
mounted(){
    console.log(this.$parent.innerFather) // ·'vm.$parent'
}

注意:有的小伙伴会发现一个问题就是既然有vm.$parent那应该也有vm.$children结果一试还真有,但是在vm中你会发现,一个父组件可以有很多个子组件,也就是有vm.$children使用这个打印出来的是一个子组件的类数组,无法定位到你要子组件的值,但是每个子组件只有一个父组件这个是固定的。

3.0 在使用选项式API时,我们可以通过this.$refs.name的方式获取指定元素或者组件,但是组合式API中就无法使用哪种方式获取。如果我们想要通过ref的方式获取组件或者元素,需要定义一个同名的Ref对象,在组件挂载后就可以访问了

<template>
    // 子组件
    <son ref='sonRef'></son>
</template>
// js
import { ref } from 'vue'
import Son from './Son.vue'
const sonRef = ref(null)
import {  defineExpose } from 'vue'
const value = ref('子组件内部值')
defineExpose({ value })

注意:ref 与 setup 中定义的变量有且只有一个不能重名且后定义的覆盖前面一个。setup组件默认是关闭的,也即通过模板ref获取到的组件的公开实例,但是获取不到子组件内部的属性,如果需要公开需要通过defineExposeAPI暴露。

三、bus总线机制(兄弟组件之间)

通过vm.$emitvm.$on通过 vm.$emit 触发当前实例上的事件,并将参数传递给监听器,通过 vm.$on 监听当前实例上的自定义事件。

简单理解为一个托管平台,这个平台必须独立于关联两个组件之外的一个vue实例,当两个组件需要传递数据的时候,发起方在平台上触发一个vm.$emit然后传递参数,接收方在平台设置一个监听vm.$on接收参数并且执行方法,方法的参数就是发送方传递过来的参数。

先定义一个托管平台,vue项目中你可以以单文件的形式在各个兄弟组件中引入,或者像我一样生成一个vue实例挂在整个Vue实例之下。

var bus = new Vue()
Vue.prototype.bus = bus
// 发送方兄弟组件触发$emit设置名字以及传递参数
mounted(){
    this.bus.$emit('SonByDauChange','I am grandSonByDau')
}
// 接收方兄弟组件监听事件,并执行回调
methods:{
    brotherData(value){
      console.log(value) // 'I am grandSonByDau'
    }
},
mounted(){
    this.bus.$on('SonByDauChange',this.brotherData)
}  

3.0 已经移除了事件总线的使用,官方建议使用mitt 等别的第三方库

四、vm.$attrsvm.$listeners

props和emit的父子组件传值的进化版。可以从父组件到孙组件的跨级传递,中间的子组件作为vm.$attrsvm.$listeners的实现载体

vm.$attrs 存放了没有被子组件props所标记的属性,即可以理解为父组件在子组件上自己绑定的数据

2.0

//  父组件引入son子组件
<div>这是父组件</div>
<Son :myData="value" :grandSon="grandSon" @changeGrandSon="changeGrandSon"></Son>
data () {
    return {
      myData:'from props',
      grandSon:'from father'
    };
},
methods: {
    changeGrandSon(value){
      console.log(value) // from grandSon
    }
},

// 子组件,能在父组件中被引用的是myData的值,但是我们能通过vm.$attr对象获得props之外的属性
<div>这是子组件</div>
<grandSon v-bind="$attrs" v-on="$listeners"></grandSon> // 划重点可以将父组件给予子组件prop的额外的值传递给孙组件
props:{
      myData:{
        type:String,
        default:''
}
mounted() {
    console.log(this.$attrs.granSon) //可以获得from father
},

// 孙组件,不需要props可以直接获得从父组件传递过来的值
<div>这是孙组件</div>
<div>{{$attrs.grandSon}}</div> // from father
 mounted() {
    console.log(this.$attrs)
    this.$emit('changeGrandSon','from grandSon') // 触发父组件的changeGrandSon的事件
},

总结:vm.$attrsvm.$listeners都可以实现从父组件将值和事件跨级(上下游,兄弟组件不可)传递,但是在中间的组件(子组件)需v-bind="$attrs" v-on="$listeners"要绑定传值。

3.0

在 Vue 3 的虚拟 DOM 中,事件监听器现在只是以 on 为前缀的 attribute,这样它就成为了 $attrs 对象的一部分,因此 $listeners 被移除了。

<template
  <label>
    <input type="text" v-bind="$attrs" />
  </label>
</template>
<script>
export default {
  inheritAttrs: false
}
</script>

如果这个组件接收一个 id attribute 和一个 v-on:close 监听器,那么 $attrs 对象现在将如下所示:

{
  id: 'my-input',
  onClose: () => console.log('close 事件被触发')
}

五、provide / inject

这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。只有数据向下传递,并没有事件响应向上。可以向下跨级传递,子、孙只要有inject均可。

2.0

// 父级组件提供 'foo'
var Provider = {
  provide: {
    foo: 'bar'
  },
  // ...
}

// 子、孙组件注入 'foo'
var Child = {
  inject: ['foo'],
  created () {
    console.log(this.foo) // => "bar"
  }
  // ...
}

3.0

// 父组件
import { ref, provide } from 'vue'
const list = ref(['冲'])
// 向子组件提供数据
provide('list', list.value)
子组件
import {inject} from 'vue'
const list = inject('list')

值得注意的是使用provide进行数据传递时,尽量readonly进行数据的包装,避免子组件修改父级传递过去的数据。

六、使用vuex和localStorage

通过使用数据存储仓库来进行组件的值得交互。建议使用vuex管理大型项目中的各个组件之间的值通讯。可以围绕vuex的状态控制来生成多个组件的关联。具体关于vuex的使用可以查询官方文档

七、结束

单纯提出想法,中间可能有坑更多的实际操作需要各位亲自去尝试,结合api文档。