Vue2通信方式-笔记

190 阅读3分钟

「这是我参与2022首次更文挑战的第11天,活动详情查看:2022首次更文挑战」。

前言

首先,我们要明白,vue组件通信主要分为:

  • 父子组件之间通信(又具体分为父组件向子组件传值、子组件向父组件传值) :props$emit$refs.syncv-model$children$parent
  • 父组件跟孙子组件通信: $arrts$listenersprovideinject
  • 非父子组件之间的通信: eventbusvuex

一、父子组件之间通信

1. props(父组件=>子组件)

// 父组件 parent.vue
<template>
  <div>
    <child :title="title" :list="list"></child>
  </div>
</template>

<script>
import child from '@/demo/child'
export default {
  components: {
    child
  },
  data () {
    return {
      title: '事项',
      list: ['吃早饭', '刷剧', '码代码']
    };
  },
};
</script>
// 子组件 child.vue
<template>
  <div>
    <p>{{title}}</p>
    <ul>
      <li v-for="item in list" :key="item">{{item}}</li>
    </ul>
  </div>
</template>

<script>
export default {
  props: {
    title: {
      type: String, // props校验
      default: '标题'  // 默认值
    },
    list: {
      type: Array,
      default: () => ['吃饭', '睡觉', '打豆豆']
    }
  },
};
</script>

传值和未传值效果图

微信截图_20220213185649.png

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

2.$emit (子=>父)

// 子组件 child.vue
<template>
  <div>
    <p>{{title}}</p>
    <ul>
      <li v-for="item in list" :key="item" @click="clickItem(item)">{{item}}</li>
    </ul>
  </div>
</template>

<script>
export default {
  data () {
    return {
    };
  },
  props: {
    title: {
      type: String, // props校验
      default: '标题'  // 默认值
    },
    list: {
      type: Array,
      default: () => ['吃饭', '睡觉', '打豆豆']
    }
  },
  methods: {
    fn () {
      console.log('9999999999');
    },
    clickItem (item) {
      // 传多个 传方法
      // this.$emit('clickItem', [item, this.fn])
      this.$emit('clickItem', item)
    }
  },
};
</script>
// 父组件 parent.vue
<template>
  <div>
    <child @clickItem="clickItem"></child>
    <p>点击了: {{item}}</p>
  </div>
</template>

<script>
import child from '@/demo/child'
export default {
  components: {
    child
  },
  data () {
    return {
      title: '事项',
      list: ['吃早饭', '刷剧', '码代码'],
      item: ''
    };
  },
  methods: {
    clickItem (item) {
      this.item = item
    }
  },
};
</script>

效果图

捕获.PNG

子组件 $emit 注册事件 第一个参数是事件名字,父组件要保持一致,多个传值,传一个数组,可以传递方法

3. .sync (父子双向通信)

在有些情况下,我们可能需要对一个 prop 进行“双向绑定”。这时候 .sync 就上场了。.sync 修饰符是作为一个编译时的语法糖存在。它会被扩展为一个自动更新父组件属性的 v-on 监听器

<child :isShow.sync="showValue"></child> // 是语法糖,最后会被解析成
<child :isShow="showValue" @update:isShow="val => showValue = val"></child>

然后子组件就这样向父组件传值即可

this.$emit('update:isShow', newValue)
// isShow是要修改的键  newVaule是值

代码示例

// 父组件 parent.vue
<template>
  <div>
    <p>这是一行标题</p>
    <child :isShow.sync="isShow"></child>
  </div>
</template>

<script>
import child from '@/demo/child'
export default {
  components: {
    child
  },
  data () {
    return {
      isShow: true
    };
  },
};
</script>
// 子组件 child.vue
<template>
  <div>
    <el-button @click="toggleShow">显示/隐藏详情</el-button>
    <p v-if="isShow">这是一段详情。这是一段详情。这是一段详情。</p>
  </div>
</template>

<script>
export default {
  props: {
    isShow: {
      type: Boolean, // props校验
    }
  },
  methods: {
    toggleShow () {
      // 直接修改props的值会报错 通过触发父组件.sync的update方法传值并修改
      // 实现父传值给子 子修改并赋值
      this.$emit('update:isShow', !this.isShow)
    }
  },
};
</script>

4. v-model(父子双向通信)

v-model可以在自定义组件上使用,可以实现双向通信。v-model其实也是个语法糖。比如说,

<input v-model="something">// 是语法糖,最后会被解析成 <input :value="something" @input="something = $event.target.value">
// 父组件 parent.vue
<template>
  <div>
    <p>这是一行标题</p>
    <child v-model="isShow"></child>
  </div>
</template>

<script>
import child from '@/demo/child'
export default {
  components: {
    child
  },
  data () {
    return {
      isShow: true
    };
  },
};
</script>
// 子组件 child.vue
<template>
  <div>
    <button @click="toggleShow">显示/隐藏详情</button>
    <p v-if="value">这是一段详情。这是一段详情。这是一段详情。</p>
  </div>
</template>

<script>
export default {
  data () {
    return {
    };
  },
  props: ['value'], //接收一个 value prop,注意,这里用的是value
  methods: {
    toggleShow () {
      // 直接修改props的值会报错 通过触发父组件v-model的input方法传值并修改
      // 实现父传值给子 子修改并赋值
      this.$emit('input', !this.value)
    }
  },
};
</script>

5. ref(父调用子的方法或访问子的数据)

ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果在子组件上引用,就指向组件实例,我们可以通过实例直接调用组件的方法或访问数据, 下面举个例子,如何通过父组件获取子组件的值 以及 控制子组件的值+1

// 父组件 parent.vue
<template>
  <div>
    <child ref="child"></child>
    <button @click="clickBtn">点击+1</button>
  </div>
</template>

<script>
import child from '@/demo/child'
export default {
  components: {
    child
  },
  methods: {
    clickBtn () {
      console.log(this.$refs.child); // 拿到组件实例
      // this.$refs.child.num = 10  // 直接修改
      this.$refs.child.changeNum()  // 调子方法
    }
  },
};
</script>
// 子组件 child.vue
<template>
  <div>
    <p>{{num}}</p>
  </div>
</template>

<script>
export default {
  data () {
    return {
      num: 0
    };
  },
  methods: {
    changeNum () {
      this.num++
    }
  },
};
</script>

6.$children$parent

// 父组件 parent.vue
<template>
  <div>
    <child></child>
    <button @click="clickBtn">点击+1</button>
  </div>
</template>

<script>
import child from '@/demo/child'
export default {
  components: {
    child
  },
  data () {
    return {
      msg: '父组件的信息'
    }
  },
  methods: {
    clickBtn () {
      this.$children[0].changeNum()  // 调子方法 $children返回的是一个数组
    }
  },
};
</script>
// 子组件 child.vue
<template>
  <div>
    <p>{{num}}</p>
    <p>{{msg}}</p>
  </div>
</template>

<script>
export default {
  data () {
    return {
      num: 0
    };
  },
  computed: {
    msg () {
      return this.$parent.msg   // $parent是一个对象
    }
  },
  methods: {
    changeNum () {
      this.num++
    }
  },
};
</script>

注意:

  1. this.$parentthis.$children返回的值不一样,this.$children 的值是数组,而this.$parent是个对象
  2. 在#app上的this.$parent得到的是new Vue()的实例,在这实例上再this.$parent得到的是undefined,而在最底层的子组件的this.$children是个空数组

二、父-子-孙组件之间通信

1. $attr$listeners

当我们写高级别的组件的时候,如果有N个props以及N个$emit触发的事件,可以用$attr$listeners轻松解决,否则的话每一个从父组件传到子组件的props,我们都得在子组件的 props 中显式的声明才能使用。

这样一来,我们的子组件每次都需要申明一大堆 props. 遇到多级组件嵌套的情况代码会显得非常的冗余,有了$attr$listeners不用将 props 一层一层往下传递。

$attrs、$listeners 都是可以跨域父子组件,可以父 - 子 - 孙组件传递。下面举个例子,用$attrs、$listeners实现父组件parent.vue跟孙子组件grandson.vue的通信。

// 父组件 parent.vue
<template>
  <div>
    <child :message="message" :number="number" @upNumber="upNumber" @input="upMsg" />
  </div>
</template>

<script>
import child from '@/demo/child'
export default {
  components: {
    child
  },
  data () {
    return {
      message: "parent",
      number: 0
    }
  },
  methods: {
    upNumber (value) {
      this.number = value;
    },
    upMsg (value) {
      this.message = value;
    },
  },
};
</script>
// 子组件 child.vue
<template>
  <grandson v-bind="$attrs" v-on="$listeners" />
</template>

<script>
import grandson from "@/demo//grandson";
export default {
  inheritAttrs: false,
  components: {
    grandson
  },
};
</script>
// 孙子组件 grandson.vue
<template>
  <div class="children" style="font-size:18px">
    {{$attrs.message}}
    <p @click="$listeners.upNumber($attrs.number + 1)">点击数字实现递增{{$attrs.number}}</p>
  </div>
</template>

<script>
export default {
  inheritAttrs: false,
  mounted () {
    console.log(this.$attrs); // 不包含class 和 style  参数data
    console.log(this.$listeners);  // 方法对象
    setTimeout(() => {
      // 用$emit跟$listeners都行
      // this.$emit("input", "children");
      // this.$emit('upNumber', this.$attrs.number + 1)
      this.$listeners.input("children")
      this.$listeners.upNumber(this.$attrs.number + 1)
    }, 1500);
  }
};
</script>

注意: $attr包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (classstyle 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (classstyle 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。

inheritAttrs

inheritAttrs 默认是true, 如果没设置为false的话,父作用域的不被认作 props 的特性绑定将会“回退”且作为普通的 HTML 特性应用在子组件的根元素上。如果我们设置 inheritAttrs:false,这些默认行为将会被去掉。是不是觉得特别晦涩难懂,别慌~ 看下面对比图,相信你就懂了!

捕获.PNG

2. provideinject

provide/ inject 是vue2.2.0新增的api, 简单点来说就是父组件中通过provide来提供变量, 然后再子组件中通过inject来注入变量。

provideinject 主要在开发高阶插件/组件库时使用。并不推荐用于普通应用程序代码中。 provideinject允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。

// 父组件 parent.vue
<template>
  <div class="parent">
    <child />
  </div>
</template>

<script>
import child from "@/demo/child";
export default {
  components: {
    child
  },
  provide: {
    message: 'parent',
    fn () {
      console.log('999999999999');
    }
  }
};
</script>
// 子组件 child.vue
<template>
  <div>
    <Grandson />
    <p>{{message}}</p>
  </div>

</template>

<script>
import Grandson from "@/demo/grandson";
export default {
  components: {
    Grandson
  },
  inject: ['message', 'fn'],
  mounted () {
    console.log(this.fn);
  }
};
</script>
// 孙子组件 grandson.vue
<template>
  <div>
    {{message}}
  </div>
</template>

<script>
export default {
  inject: ['message', 'fn'],
  mounted () {
    console.log(this.fn);
  }
};
</script>

三、非父子组件之间通信:

1.eventbus

eventBus又称为事件总线,在vue中可以使用它来作为沟通桥梁, 就像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件, 所以组件都可以通知其他组件。

针对中大型项目, 首选Vuex, 但是如果是小型项目使用Vue的eventBus, 是一个不错的选择。

全局的eventBus简单理解为在一个文件创建一个新的vue实例然后暴露出去, 使用的时候import这个模块进来即可。

我们在来实现comp2.vuecomp1.vue传递数据。做个简单的累加器。

// event-bus.js
import Vue from 'vue'
export const EventBus = new Vue()
// parent.vue 
<template>
  <div>
    <comp1></comp1>
    <comp2></comp2>
  </div>
</template>

<script>
import comp1 from '@/components/comp1'
import comp2 from '@/components/comp2'
export default {
  components: {
    comp1,
    comp2
  }
};
</script>
// comp2.vue 发送事件
<template>
  <div>
    <button @click="additionHandle">+累加</button>
  </div>
</template>

<script>
import { EventBus } from '@/util/event-bus.js'
export default {
  data () {
    return {
      num: 1
    };
  },
  methods: {
    fn () {
      console.log('999999999999');
    },
    additionHandle () {
      EventBus.$emit('add', {
        num: this.num++
      })
      // EventBus.$emit('add', this.fn) 传函数
      // EventBus.$emit('add', 'text') 多参传数组
    }
  },
};
</script>
// comp1.vue 接收事件
<template>
  <div>计算和: {{count}}</div>
</template>

<script>
import { EventBus } from '@/util/event-bus.js'
export default {
  data () {
    return {
      count: ''
    };
  },

  mounted () {
    EventBus.$on('add', param => {
      this.count = param.num;
      // console.log(param);
      // this.fn(param)
    })
  },

  methods: {
    fn (item) {
      console.log(item);
    },
  },
};
</script>

缺点: 当项目较大, eventBus难以维护

2.vuex

vuex这里就不赘述了,建议直接移步官方文档

总结

下面,再来一张思维导图来阐述通信的主要方式。

捕获.PNG

写在最后

学习英语好处很多
props 中: 道具
emit 中: 发出 发射
sync 中: 同时,同步;协调,一致
model 中: 模型;模式;
parent 和 children 中: 父亲 儿子
attr 中: 属性(attribute)
listeners 中: 听众;监听器(listener 的复数)
provide 中: 提供,供给;配备,准备好;
inject 中: 注射;(给……)添加,增加(某品质)
eventbus 中: enent 事件 bus 公共汽车
感谢 juejin.cn/post/696380…