Vue组件通信方式汇总

930 阅读3分钟

组件通信

prop

子组件设置props属性,定义接收父组件传递过来的参数

<template>
  <div>
    <ChildA name="vue" :age="20"></ChildA>
  </div>
</template>
<template>
  <div>
    <b>{{ name }}</b>
    <br>
    <b>{{ age }}</b>
  </div>
</template>

<script>
export default {
  name: "ChildA",
  props: {
    name: String,
    age: {
      type: Number,
      default: 20,
      require: true
    }
  },
}
</script>

$emit 触发自定义事件

子组件通过emit触发自定义事件,emit触发自定义事件,emit第二个参数为传递的数值

父组件绑定监听器获取到子组件传递过来的参数

<template>
  <div>
    <ChildA @test="test"></ChildA>
  </div>
</template>

<script>
import ChildA from './ChildA.vue'

export default {
  components: {ChildA},
  methods: {
    test(msg) {
      alert(msg)
    }
  }
}
</script>
<template>
  <div>
    <a @click="click">Click</a>
  </div>
</template>

<script>
export default {
  name: "ChildA",
  methods:{
    click(){
      this.$emit("test","OK")
    }
  }
}
</script>

事件总线

事件总线(Event Bus)是一种非常常用的通信方式。它允许在不同组件之间进行通信,无论它们之间是否存在父子关系或兄弟关系。它的原理是利用 Vue 实例作为中央事件总线来进行组件之间的事件通信。

在main.js中创建一个新的 Vue 实例,作为全局事件总线实例,并将其作为 Vue 实例的属性

import Vue from 'vue';

Vue.prototype.$bus = new Vue();

发送事件的组件中使用 $emit 方法触发事件

export default {
  methods: {
    handleClick() {
      this.$bus.$emit('my-event', 'Hello, world!');
    }
  }
}

在接收事件的组件中使用 $on 方法监听事件

export default {
  methods: {
    handleEvent(data) {
      console.log(data)
    }
  },
  mounted() {
    this.$bus.$on('my-event', this.handleEvent);
  },
  beforeDestroy() {
  	# 取消对事件的监听
    this.$bus.$off('my-event', this.handleEvent);
  }
} 

vuex

Vuex是一个专门为 Vue应用程序开发的状态管理库,它可以更好地管理应用程序中的数据,并实现组件间的通信。使用 Vuex 可以使得多个组件共享同一个状态,方便进行数据的传递和同步。

1.创建store.js

指定一个名为 message 的状态属性,以及一个名为 setMessage 的 mutation 方法,可以修改 message 的值

import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

const store = new Vuex.Store({
  state: {
    message: ''
  },
  mutations: {
    setMessage(state, payload) {
      state.message = payload;
    }
  }
});

export default store;

2.在需要修改状态的组件中使用 Vuex Store 中的 mutation 方法

在ComponentA.vue,使用 $store.commit 方法来调用 mutation 方法,从而修改 Vuex Store 中的状态

<template>
  <div>
    <input v-model="message" />
    <button @click="setMessage">Set Message</button>
  </div>
</template>

<script>
import { mapState } from 'vuex';

export default {
  computed: {
    ...mapState(['message'])
  },
  methods: {
    setMessage() {
      this.$store.commit('setMessage', this.message);
    }
  }
};
</script>

3.在需要使用状态的组件中使用 Vuex Store 中的数据。

创建ComponentB.vue,以使用 Vue 的 computed 或 mapState 属性来访问 Vuex Store 中的数据

template>
  <div>
    <p>{{ message }}</p>
    <p>{{this.$store.state.message}}</p>
  </div>
</template>

<script>
import { mapState } from 'vuex';

export default {
  computed: {
    ...mapState(['message'])
  }
};
</script>

5.mapState

mapState是 Vuex 的一个辅助函,帮助在Vue 组件中快速的将 state 中的属性映射为组件的 computed 计算属性,可以直接在组件的 template 中使用

{{ message }} 就是通过 mapState 辅助函数生成的计算属性,它实际上等价于:

computed: {
  message() {
    return this.$store.state.message
  }
}

parent/parent/root组件通信

兄弟组件之间通信可通过共同祖辈搭桥,parentparent或root。

$parent:可以通过 $parent 属性来访问当前组件的父组件,从而实现父子组件之间的通信

$root:可以通过 $root 属性来访问当前组件所在的根实例,从而实现祖先和后代组件之间的通信

$children: 可以用于访问当前组件的直接子组件

注意:使用 $children 有一定的风险,因为它不保证子组件的顺序和数量。如果需要访问指定的子组件,建议使用 ref 属性来指定组件的名称。

使用 parentparent 或 root 属性来在兄弟组件之间进行通信

定义一个父组件 Parent,它包含了两个子组件 ChildA 和 ChildB

<template>
  <div>
    <ChildA/>
    <ChildB/>
  </div>
</template>

<script>
import ChildA from './ChildA.vue'
import ChildB from './ChildB.vue'

export default {
  components: {
    ChildA,
    ChildB
  }
}
</script>

点击按钮,通过 $parent 属性访问父组件 Parent,然后通过父组件访问 ChildB 组件,并调用 ChildB 组件的 receiveMessage 方法,将消息传递给 ChildB 组件。

<template>
  <div>
    <button @click="click">兄弟组件通信</button>
  </div>
</template>

<script>
export default {
  name: "ChildA",
  data() {
    return {}
  },
  methods: {
    click() {
      // 通过 $parent 访问父组件,再通过父组件访问 ChildB
      this.$parent.$children.find(child => child.$options.name === 'ChildB').receiveMessage('Hello ChildB')
    }
  }
}
</script>

ChildB 组件接收到消息后会将消息显示在页面上

<template>
  <div>
  </div>
</template>

<script>
export default {
  name: "ChildB",
  data() {
    return {
      message: 'B组件'
    }
  },

  methods: {
    receiveMessage(msg) {
      alert(msg)
    }
  }
}
</script>

使用this.$parent.$emitthis.$parent.$on

<template>
  <div>
    <button @click="click">兄弟组件通信</button>
  </div>
</template>

<script>
export default {
  name: "ChildA",
  data() {
    return {}
  },
  methods: {
    click() {
      this.$parent.$emit('send', "Hello World")
    }
  }
}
<template>
  <div>
  </div>
</template>

<script>
export default {
  name: "ChildB",
  data() {
    return {
      message: 'B组件'
    }
  },

  methods: {
    onEvent(msg) {
      alert(msg)
    }
  },
  created() {
    this.$parent.$on('send', this.onEvent)
  }
}
</script>

$children

父组件可以通过$children访问子组件实现父子通信

当前实例的直接子组件。$children 并不保证顺序,也不是响应式的。

<script>
export default {
  name: "ChildA",
  data(){
    return{

    }
  },
  methods: {
    test() {
      alert("A组件")
    }
  }
}
</script>
<script>
export default {
  name: "ChildB",
  data() {
    return {
      message: 'B组件'
    }
  }
}
</script>
<template>
  <div>
    <ChildA/>
    <ChildB/>
    <button @click="click">获取子组件信息</button>
  </div>
</template>

<script>
import ChildA from './ChildA.vue'
import ChildB from './ChildB.vue'

export default {
  components: {
    ChildA,
    ChildB
  },
  methods: {
    click() {
      // 访问第一个子组件的message属性
      this.$children[0].test()
      // 访问第二个子组件的test方法
      console.log(this.$children[1].message);
    }
  }
}
</script>

$attrs

包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)

通过 v-bind="$attrs" 传入内部组件

$attrs能接收除了props声明外的所有绑定属性,一旦属性在props中被声明了,就不能通过$attrs获取

$ attrs可以让孙子和儿子组件访问到父组件的属性

<template>
  <div>
    <ChildA msg="hello world" v-bind="$attrs"/>
  </div>
</template>

msg并未在props中声明

<template>
  <div>
    <p>{{$attrs.msg}}</p>
  </div>
</template>

$listeners

包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件

$ listeners可以让孙子和儿子组件访问到父组件的方法

<template>
  <div>
    <ChildA @click1.native="click1" @click2="click2" v-bind="$listeners"/>
  </div>
</template>

<script>
import ChildA from './ChildA.vue'

export default {
  components: {ChildA},
  methods: {
    click1() {
      alert("click1")
    },
    click2() {
      alert("click2")
    }
  }
}
</script>

点击click事件,则执行父组件的click2方法

<template>
  <div>
    <a @click="click">click</a>
  </div>
</template>

<script>
export default {
  name: "ChildA",
  data() {
    return {}
  },
  methods: {
    click() {
      this.$emit('click1')
      this.$emit('click2')
    }
  }
}
</script>

$refs

一个对象,持有注册过 ref attribute 的所有 DOM 元素和组件实例。

父组件使用子组件的时候设置ref 父组件通过设置子组件ref获取数据

<template>
  <div>
    <ChildA ref="child"/>
  </div>
</template>

<script>
import ChildA from './ChildA.vue'

export default {
  components: {ChildA},
  mounted() {
    alert(this.$refs.child.msg)
  }
}
</script>
<template>
  <div>
    <p>{{ msg }}</p>
  </div>
</template>

<script>
export default {
  name: "ChildA",
  data() {
    return {
      msg: "Hello World"
    }
  },
}
</script>

provide / inject

provide 和 inject 可以用来在祖先组件中指定想要提供给后代组件的数据或方法,而在任何后代组件中,都可以使用 inject 来接收 provide 提供的数据或方法

能够实现祖先和后代之间传值

<template>
  <div>
    <ChildA/>
  </div>
</template>

<script>
import ChildA from './ChildA.vue'

export default {
  components: {ChildA},
  data() {
    return {
      msg: "OK"
    }
  },
  provide() {
    return {
      msg: "OK",
      click: this.test
    }
  },
  methods: {
    test() {
      alert("OK")
    }
  }
}
</script>
<template>
  <div>
    <b>{{ msg }}</b>
    <hr>
    <a @click="click">click</a>
  </div>
</template>

<script>
export default {
  name: "ChildA",
  data() {
    return {}
  },
  inject: ['msg','click'],
}
</script>

插槽

插槽语法是Vue 实现的内容分发 API,⽤于复合组件开发。

匿名插槽

<template>
  <div>
    <ChildA>Hello</ChildA>
  </div>
</template>

<script>
import ChildA from './ChildA.vue'

export default {
  components: {ChildA},
}
</script>
<template>
  <div>
    <slot></slot>
  </div>
</template>

<script>
export default {
  name: "ChildA",
}
</script>

具名插槽

将内容分发到⼦组件指定位置

<template>
  <div>
    <ChildA>
      <!-- 默认插槽⽤default做参数 -->
      <template v-slot:default>默认插槽</template>
      <!-- 具名插槽⽤插槽名做参数 -->
      <template v-slot:content>具名插槽</template>
    </ChildA>
  </div>
</template>

<script>
import ChildA from './ChildA.vue'

export default {
  components: {ChildA},
}
</script>
<template>
  <div>
    <slot></slot>
    <br>
    <slot name="content"></slot>
  </div>
</template>

<script>
export default {
  name: "ChildA",
}
</script>

作⽤域插槽

在父组件中使用作用域插槽来访问子组件中的数据

子组件定义了一个带有名为user的数据的插槽

<template>
  <div>
    <slot :user="user"></slot>
  </div>
</template>

<script>
export default {
  name: "ChildA",
  data(){
    return{
      user:{"name":"ChildA","age":20}
    }
  }
}
</script>

在父组件中,我们使用v-slot指令来访问子组件传递过来的数据,并将其显示出来。

<template>
  <div>
    <ChildA>
      <!-- 把v-slot的值指定为作⽤域上下⽂对象 -->
      <template v-slot:default="slotProps">
        获取⼦组件数据:<br>
        姓名: {{slotProps.user.name}}<br>
        年龄: {{slotProps.user.age}}<br>
      </template>
    </ChildA>
  </div>
</template>