从点到面带汤的吃透vue2中的组件化与通信

112 阅读3分钟

这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战

讲通信,那么我们为什么要通信呀?

我们一般使⽤独⽴可复⽤的组件来构建⼤型应⽤,任意类型的应⽤界⾯都可以抽象为⼀个组件树,而在vue中使用组件化开发会变得更容易

那么为什么要使用组件化?

  1. 提⾼开发效率
  2. ⽅便重复使⽤
  3. 简化调试步骤
  4. 提升项⽬可维护性
  5. 便于多⼈协同开发

image.png

而涉及组件化的同时,必然会涉及通信,则是今天会讲到的知识点

vue2中通信大致分为以下几种:

  1. props
  2. emit/emit/on
  3. eventBus
  4. $parent
  5. $children
  6. $root
  7. $refs
  8. provide/inject
  9. $attrs
  10. $listeners
  11. vuex

props

子组件接收父组件的值使用props parent

<HelloWorld msg="hello world"/>

child

props: { msg: String }

msg可以指定type类型,require是否必须,default默认值等

需要注意的是如果数组默认为空需要用default:()=>{ return [] }

or

props: ['msg']

emit/emit/on

父子组件之间进行通信

parent

// template
<HelloWorld @sendMsg="sendMsg" />
// script
methods:{
    sendMsg(msg){
        console.log(msg) // this is msg
    }
}

child

this.$emit("sendMsg","this is msg")

eventBus

eventBus,任意两个组件之间通信可以使用事件总线,就像打电话一样

  1. 手写eventBus
// bus.js
class Bus {
  constructor() {
    this.callbacks = {};
  }

  $on(name, fn) {
    this.callbacks[name] = this.callbacks[name] || [];

    this.callbacks[name].push(fn);
  }

  $emit(name, args) {
    if (this.callbacks[name]) {
      this.callbacks[name].forEach((cb) => cb(args));
    }
  }
}

// main.js
Vue.prototype.$bus = new Bus();

// child
this.$bus.$on('sendMsg',msg =>{ console.log(msg) })
this.$bus.$emit('sendMsg','this is msg')
  1. 新建一个eventBus.js文件
// eventBus.js
import Vue from 'vue'
export const EventBus = new Vue()

发送者

<script> 
import { EventBus } from "../eventBus.js";
export default {
  mounted() {
    sendMsg() {
      EventBus.$emit("sendMsg", 'this is msg');
    }
  }
}; 
</script>

接收者

import { EventBus } from "../eventBus.js";
export default {
  mounted() {
    EventBus.$on("sendMsg", (msg) => {
      console.log(msg) // this is msg
    });
  }
}; 
</script>
  1. 直接使用 挂载
// main.js
Vue.prototype.$EventBus = new Vue()

使用

// 发送
this.$EventBus.$emit("sendMsg", 'this is msg')
// 接收
this.$EventBus.$on("sendMsg", (msg) => {
  console.log(msg) // this is msg
});

parent/parent/root

兄弟组件之间通信可通过共同祖辈搭桥

// brother1
this.$parent.$on('msg', handle)

// brother2
this.$parent.$emit('msg')

兄弟组件可以使用 $parent,没有直接关系使用 $root

有时不太复杂的逻辑没必要通过vuex管理,这个时候可以用$root可以获取父组件data中的值

parent

  data() {
    return {
        msg: "this is msg"
    };
  },

child

console.log(this.$root.msg)

$children

⽗组件同样可以通过$children访问⼦组件实现⽗⼦通信

// parent
this.$children[0].xx = 'xxx'

注意:$children不能保证⼦元素顺序,因此官方并不推荐用

attrs/attrs/listeners

特性是⽗作⽤域中不作为 prop 被识别,通过 vbind="$attrs" 直接传⼊内部组件,这种用法比较少见,一般在创建高阶组件时会用到

  1. $attrs

grandpa

<Child msg="this is msg"></Child1>

child

<p>{{$attrs.msg}}</p>
<Grandson v-bind="$attrs" v-on="$listeners"></Grandson>

grandson

<div>{{msg}}</div>
console.log(msg) // this is msg

当grandpa传递给child,也就是gandson的父级时,并不需要在props里定义,通过vbind="$attrs"直接传递给grandson

传递中v-bind可以将对象展开,父组件也可以使用

因为grandson的父组件可能压根不需要这些参数,通过这种方法,子孙组件可以直接获取到来自祖辈传递的值

  1. $listeners

grandson

 <div @click="$emit('send-msg', 'this is grandson')"></div>

parent

<Grandson v-on="$listeners"></Grandson>

grandpa

// template
<Child @send-msg="sendSomeEvent" />

// methods
sendSomeEvent(msg){
    console.log(msg) // this is grandson
}

同样的,当子孙想把消息传递给祖辈时,父组件也会在中间搭一座桥,通过v-on="$listeners"直接传递给祖辈

provide/inject

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

grandpa

export default {
  provide() {
    return {
      foo: 'foo from grandpa',
    }
  },
}

grandson

// template
<div> {{foo}} </div>

// script
export default {
  inject: ["foo"]
}

其中涉及的生命周期顺序:

  1. data
  2. provide
  3. created,在这个阶段$el还未生成,在这先处理privide的逻辑,子孙组件才可以取到inject值
  4. mounted

还需要注意的是:

Vue 不会对 provide 中的变量进行响应式处理。所以,要想 inject 接受的变量是响应式的,provide 提供的变量本身就需要是响应式的

并且,不常用的原因其中有:

如果有多个后代组件同时依赖于一个祖先组件提供的状态,那么只要有一个组件修改了该状态,那么所有组件都会受到影响

其中涉及的强耦合,在多人协作时,可能会造成混乱,你根本不知道在哪修改了值,因此,在使用的时候一定要规划好,规定好作用域以及确认是一次性数据

那为什么又推出这个东西呢,其存在必然有道理,比如在组件开发的时候

想想封装一个Form的组件,其中有FormItem,再其中可能还会有Button,那么一层一层的关系嵌套下,无论是props还是vuex,都不再是最好方案,provide/inject将会是非常好的选择