Vue的几种通信方式

组件间通信

Vue也是从基础开始学起,边学边整理,同类文章很多,本文仅供参考。

如有不到之处,请指正。

在这里插入图片描述

组件关系:A/B、B/C、B/D为父子关系; C/D为兄弟关系; A/C为隔代关系;

其中隔代关系可能隔多代;

1.父子组件通信

A/B、B/C、B/D 均属于父子组件,实现父子组件可以通过以下方式:

  1. 父组件通过props绑定数据向子组件传递,子组件通过$emit向父组件传递。
  2. 使用 p a r e n t 获 取 当 前 组 件 的 父 组 件 ( 如 果 存 在 ) , 使 用 parent获取当前组件的父组件(如果存在),使用 parent使children获取当前组件所有子组件返回数组。官方API提示,返回的子组件不保证顺序,也不是响应式的。
  3. 使用provide/inject,父组件通过provide提供变量,在所有需要改变量的子组件或者孙子组件中通过inject注入即可使用。该方式多用于组件库开发,详情可以扒拉elementUI等开源组件库,不适用普通的业务组件开发。
  4. 使用 a t t r s / attrs/ attrs/listeners,配合inheritAttrs。(更适合隔代组件)
  5. ref使用在组件上,则返回组件实例,可以访问组件的data以及methods等;如果用在普通DOM元素上,则返回DOM节点
1.props/$emit🌟🌟
2.provide/inject
3.$ref
4.$parent/$children
5.$attrs/$listeners

2.兄弟组件通信

C/D 属于兄弟组件,要实现通信可以通过以下形式(不考虑是否合理):

  1. 使用eventBus事件总线,相对于1来说事件总线更为合适,但是代码维护性较差,可读性较低。不适合较大项目。
  2. 使用Vuex进行状态管理,如果仅仅是传递数据,不做异步处理,有显得冗余复杂。
1.eventBus
2.vuex

3.隔代组件通信

A/C、A/D 属于隔代组件,要实现通信可以通过以下形式(不考虑是否合理):

  1. A.vue使用props绑定数据并逐级传递,C.vue/D.vue 接收数据,如果C.vue/D.vue发生数据变化,则需要通过事件逐级返回通知A.vue。
  2. 使用eventBus事件总线,相对于1来说事件总线更为合适,但是代码维护性较差,可读性较低。不适合较大项目。
  3. 使用Vuex进行状态管理,如果仅仅是传递数据,不做异步处理,有显得冗余复杂。
  4. 使用 a t t r s / attrs/ attrs/listeners,配合inheritAttrs
  5. 使用provide/inject,该方式多用于组件库开发,详情可以扒拉elementUI等开源组件库,不适用普通的业务组件开发
1.$attrs/$listeners
2.provide/inject
3.vuex
4.eventBus

代码实例

1.props/$emit

<!-- father -->
<template>
  <div class="about">
    <div>{{msg}}</div>
    <children title="我来自父组件" @from-child="handler"></children>
    
  </div>
</template>
<script lang="ts">
import { Vue, Component, Prop } from "vue-property-decorator";
import Children from "../views/Children.vue";

@Component({
  components: {
    Children,
    GrandSon
  }
})
export default class Father extends Vue {
  private msg:string = "default";
  handler($event: any) {
    this.msg = $event;
  }
}
</script>
<!-- children -->
<template>
  <div>
    <button @click="handler">向父组件传值</button>
    <div>{{title}}</div>
  </div>
</template>
<script lang="ts">
import { Vue, Component, Prop } from "vue-property-decorator";

@Component({})
export default class Children extends Vue {
  @Prop() title!: string;
  handler() {
    this.$emit('from-child', '我来自子组件')
  }
}
</script>

2.$refs

<!-- father -->
<template>
  <div class="about">
    <div>{{msg}}</div>
    <!-- 通过$refs通信 -->
    <children title="我来自父组件" ref="children"></children>
    
  </div>
</template>
<script lang="ts">
import { Vue, Component, Prop } from "vue-property-decorator";
import Children from "../views/Children.vue";

@Component({
  components: {
    Children,
    GrandSon
  }
})
export default class Father extends Vue {
  msg = "我是父组件"

  mounted () {
    // 通过$refs获取子组件实例,从而可以获取data、methods等
    this.$refs.children.getProps("通过refs来自父组件");
  }

}
</script>
<!-- children -->
<template>
  <div>
    <button>向父组件传值</button>
    <div>{{title}}</div>
  </div>
</template>
<script lang="ts">
import { Vue, Component, Prop } from "vue-property-decorator";

@Component({})
export default class Children extends Vue {
  title =  "我是子组件";

  getProps(val: string) {
    this.title = val;
  }
}
</script>

3.$parent/$children

<!-- father -->
<template>
  <div class="about">
    <div>{{msg}}</div>
    <!-- $parent/$children -->
    <button @click="change">点击修改子组件内容</button>
    <children></children>
  </div>
</template>
<script lang="ts">
import { Vue, Component, Prop } from "vue-property-decorator";
import Children from "../views/Children.vue";

@Component({
  components: {
    Children,
    GrandSon
  }
})
export default class About extends Vue {
  msg = "我是父组件"
  title = "我是父组件的title"
  change() {
    // $children 返回子组件的数组
    this.$children[0].modifyMsg();
  }
}
</script>
<!-- children -->
<template>
  <div>
    <div>{{title}}</div>
  </div>
</template>
<script lang="ts">
import { Vue, Component, Prop } from "vue-property-decorator";

@Component({})
export default class Children extends Vue {
  title =  "我是子组件";

  modifyMsg(){
    this.title = this.$parent.$data.title;
  }
}
</script>

4.$attrs/$listeners

<!-- father -->
<template>
  <div class="about">
    <div>{{msg}}</div>
    <children :title="title" attr1="attr1" attr2="attr2" @receivefromgrandson="handler"></children>
  </div>
</template>
<script lang="ts">
import { Vue, Component, Prop } from "vue-property-decorator";
import Children from "../views/Children.vue";
import GrandSon from "../views/Grandson.vue";

@Component({
  components: {
    Children,
    GrandSon
  },
})
export default class About extends Vue {
  msg = "我是父组件"
  title = "我是父组件的title"
  handler($event: string) {
    this.msg = $event;
  }
}
</script>
<!-- son -->
<template>
  <div>
    <!-- $inheritAttrs没有被props注册的prop -->
    <div v-bind="$attrs">{{ title }}</div>
    <!-- v-on="$listeners"相当于桥梁,如果有重孙子组件,甚至嵌套更多层组件,那么中间层组件都要使用v-on,这样内从组件才能通知祖先组件进行更新 -->
    <children2 v-bind="$attrs" v-on="$listeners"></children2>
  </div>
</template>
<script lang="ts">
import { Vue, Component, Prop } from "vue-property-decorator";
import children2 from "../views/Grandson.vue";
@Component({
  // false表示不会将未注册的props作为html属性展示在DOM节点上
  inheritAttrs: false,
  components: {
    children2
  }
})
export default class Children extends Vue {
  @Prop() title!:string;
  created() {
    console.log('子组件',this.$listeners);
  }
}
</script>
<!-- grandson -->
<template>
  <div>
    <span>我是孙子组件:</span>
    {{ $attrs }}
    <button @click="sendtograndfather">点击隔代发送通知</button>
  </div>
</template>
<script lang="ts">
import { Vue, Component, Prop } from "vue-property-decorator";

@Component({
  inheritAttrs: false
})
export default class Grandson extends Vue {
  // component
  created() {
    console.log("孙子组件", this.$listeners);
  }
  sendtograndfather() {
    this.$emit("receivefromgrandson", "来自孙子组件的更新通知");
  }
}
</script>

5.provide/inject

<!-- father -->
<template>
  <div class="about">
    <div>{{msg}}</div>
    <children :title="title" attr1="attr1" attr2="attr2" @receivefromgrandson="handler"></children>
  </div>
</template>
<script lang="ts">
import { Vue, Component, Prop, Provide } from "vue-property-decorator";
import Children from "../views/Children.vue";
import GrandSon from "../views/Grandson.vue";

@Component({
  components: {
    Children,
    GrandSon
  },
})
export default class About extends Vue {
  msg = "我是父组件"
  title = "我是父组件的title"
  user = {name: "admin", age: 20}
  // 通过Provide向内部组件传递
  @Provide() formdata = {
    user: this.user
  }
  handler($event: string) {
    this.msg = $event;
  }
}
</script>
<!-- children -->
<template>
  <div>
    <!-- $inheritAttrs没有被props注册的prop -->
    <div v-bind="$attrs">{{ title }}</div>
    <children2 v-bind="$attrs" v-on="$listeners"></children2>
  </div>
</template>
<script lang="ts">
import { Vue, Component, Prop,Inject } from "vue-property-decorator";
import children2 from "../views/Grandson.vue";
@Component({
  // false表示不会将未注册的props作为html属性展示在DOM节点上
  inheritAttrs: false,
  components: {
    children2
  }
})
export default class Children extends Vue {
  @Prop() title!:string;
  // 内部子组件通过Inject注入
  @Inject() formdata!: object
  created() {
    console.log('子组件',this.$listeners);
    console.log('获取注入的user', this.formdata)
  }
}
</script>

6.eventBus

<!-- 调用端 -->
<template>
  <div>
    <!-- $inheritAttrs没有被props注册的prop -->
    <div v-bind="$attrs">{{ title }}</div>
    <button @click="handler">通过eventBus通信</button>
    <children2 v-bind="$attrs" v-on="$listeners"></children2>
  </div>
</template>
<script lang="ts">
import { Vue, Component, Prop,Inject } from "vue-property-decorator";
import children2 from "../views/Grandson.vue";
// 引入bus!!!!!!!!!!!!!!!!!!!!!
import bus from "../util/bus.js"
@Component({
  // false表示不会将未注册的props作为html属性展示在DOM节点上
  inheritAttrs: false,
  components: {
    children2
  }
})
export default class Children extends Vue {
  @Prop() title!:string;
  created() {
    console.log('子组件',this.$listeners);
  }
  handler() {
    // 通过bus发起事件
    bus.$emit("test", "通过bus通信")
  }
}
</script>
<!-- 接收端 -->
<template>
  <div class="about">
    <div>{{ msg }}</div>
    <children
      :title="title"
      attr1="attr1"
      attr2="attr2"
      @receivefromgrandson="handler"
    ></children>
  </div>
</template>
<script lang="ts">
import { Vue, Component, Prop,Provide } from "vue-property-decorator";
import Children from "../views/Children.vue";
import bus from "../util/bus.js"

@Component({
  components: {
    Children
  },
})
export default class About extends Vue {
  msg = "我是父组件"
  title = "我是父组件的title"
  mounted () {
    let that = this;
    // 接收端通过$on监听指定的时间名称
    bus.$on("test", function(data) {
      that.msg = data;
    })
  }
  handler($event: string) {
    this.msg = $event;
  }
}
</script>

7.vuex

state 存放状态

mutations 操作state

getters 处理state成员并返回

actions 异步操作处理state

modules 模块化

img

如果vue组件中存在异步操作,需要通过dispatch派发actions中的方法,在actions的方法中commit提交mutations,从而修改state中的数据状态,保证数据的同步。

this.$store.dispatch('add', 10)

如果不存在异步操作,可以在组件中直接commit提交mutations。

this.$store.commit('add');

注意:不可以直接修改state中的数据状态。

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

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    counter: 0
  },
  mutations: {
    // payload是传递进来的参数
    add(state, payload) {
      state.counter++;
      state.counter += payload;
    }
  },
  actions: {
    // 第一个参数是vuex上下文
    // payload是传递进来的参数{}
    add({ commit, dispatch }, payload) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          commit('add', payload)
        }, 1000);
      });
    }
  },
  // 模块化
  modules: {
    
  }
})

模块化就是根据业务创建不同的模块,通过modules统一引入。模块化后需要加上命名空间进行访问!!!

import Vue from 'vue'
import Vuex from 'vuex'
// 引入子模块的vuex
import counter from "./counter";

Vue.use(Vuex)

export default new Vuex.Store({
  // 模块化
  modules: {
    counter
  },
  strict: true
})
// counter.js
export default {
  // 设置独立的命名空间,避免命名冲突
  namespaced: true,
  state: {
    counter: 0
  },
  mutations: {
    // payload是传递进来的参数
    add(state, payload) {
      state.counter++;
      state.counter += payload;
    }
  },
  actions: {
    // 第一个参数是vuex上下文
    // payload是传递进来的参数{}
    add({ commit, dispatch }, payload) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          commit('add', payload)
        }, 1000);
      });
    }
  }
}

使用了模块化后访问state或者异步处理调用actions等都需要加上对应的命名空间:

<!-- 访问state -->
{{$store.state.counter.counter}}
this.$store.dispatch('counter/add', {})

模块化后导致了访问store的路径很繁琐,所以需要将访问路径进行映射,简化写法:

import { mapState, mapMutations, mapActions } from "vuex";
<!-- 映射state -->
<template>
  <div>
    <div>我是父组件,当前计数:{{counter}}</div>
    <children1></children1>
  </div>
</template>
<script lang="ts">
import { Vue, Component, Prop } from "vue-property-decorator";
import children1 from "../views/Children.vue";
import { mapState, mapMutations, mapActions } from "vuex";
@Component({
  components: {
    children1
  },
  // 通过...mapState设定好指定的命名空间,讲该命名空间下的state返回。
  computed: {
    ...mapState('counter', {
      counter: state => state.counter
    })
  },
})
export default class NAME extends Vue {
  // component
  
}
</script>
<!-- 映射actions -->
<template>
  <div>
    <button @click="handler">我是子组件,点击更新计数器</button>
    <children2 v-bind="$attrs" v-on="$listeners"></children2>
  </div>
</template>
<script lang="ts">
import { Vue, Component, Prop,Inject } from "vue-property-decorator";
import children2 from "../views/Grandson.vue";
import { mapActions } from "vuex";
@Component({
  inheritAttrs: false,
  components: {
    children2
  },
  methods: {
  // 将actions或者mutations中的方法结构放到当前上下文中
    ...mapActions('counter', ['add'])
  },
})
export default class Children extends Vue {

  handler() {
    // this.$store.commit('add', 10);
		
    // 对比模块化以后的映射actions方法。mutations 中的映射方式一样
    // this.$store.dispatch('counter/add', 200)
    this['add'](200);
  }
}
</script>

vuex部分写的有点乱,没有办法直接跑起来。

其他部分可以直接测试。

vuex API

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小于___

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值