阅读 74

vue-组件通信

组件声明

  • 分类:全局组件 局部组件
  • 组件化开发有点:抽离组件实现复用;代码便于维护;便于组件级更新,每一个组件都有一个watcher(能抽离尽量抽离,减少更新)
    Vue.component('my-component',{
      data() { // data必须是一个函数,防止组件之间数据相互引用
        return {
          msg: 'hello',
        }
      },
      template: "<div>{{msg}}</div>"
    })
复制代码

组件的实例化过程:根据传入对象,创建一个实例

  • Vue.component默认调用vue.extend,返回传递对象的构造函数,通过这个构造函数可以手动的挂载组件
  • vue.extend的原理:将传递的组件转为对象;创建一个子类,继承Vue的原型,并将参数传递给子类;在子类中会调用_init方法实现渲染,最终返回子类;多次使用同一个组件extend时,返回同一个类
    let Ctor = Vue.extend({
      data() {
        return {
          msg: 'hello',
        }
      },
      template: "<div>{{msg}}</div>"
    })
    document.body.appendChild(new Ctor().$mount().$el) // 手动挂载组件
复制代码

父子组件间通信

安装@vue/cli、@vue/cli-service-global快速原型工具,执行vue serve启动服务;实现零配置

组件使用规则:定义 引入 注册 使用,注册和使用时需要名字一致

通信方式:属性传递方式 事件绑定方式 语法糖方式v-model .sync

属性传递-props

父子组件间,父组件将属性和改变属性的方法fn传递给子组件,子组件进行接收;子组件通过调用传递的fn方法改变父组件的属性

    // 父组件
    <template>
      <div>Parent
        <Son1 :money='mny' :change-money='changeMoney'></Son1>
      </div>
    </template>
   
    components: { Son1 },
    data() { return { mny: 100 } },
    methods: { // methods中的函数已经被bind过了,不管谁调用它的方法,函数中的this都是当前组件实例
        changeMoney(value) { this.mny += value },
    }
    -----------------------------
    // 子组件
    <div> son1 {{money}}
        <button @click='changeMoney(500)'>更改父亲</button>
    </div>
    
    props: {
        money: { type: Number, default: 100 },
        changeMoney: { type: Function, default: ()=> {} }
    }
    
复制代码

事件绑定方式$emit

在父组件中,给子组件增加事件cl;在子组件中,通过$emit方式触发cl事件

    // 父组件
    <template>
      <div>Parent
        <Son1 :money='mny' @cl='changeMoney'></Son1>
      </div>
    </template>
   
    components: { Son1 },
    data() { return { mny: 100 } },
    methods: {
        changeMoney(value) { this.mny += value },
    }
    -----------------------------
    // 子组件
    <div>son1 {{money}}
        <button @cl='$emit("cl", 500)'>更改父亲</button>
    </div>
    
    props: {
        money: { type: Number, default: 100 },
    }
复制代码

v-model方式同步数据

在父组件中,直接使用v-model,相当于v-bind:value+v-on:input;在子组件中,触发input事件,传递参数

    // 父组件
    <template>
      <div>Parent
        <!--  <Son1 :value='mny' @input='value=>mny=value'></Son1> -->
        <Son1 v-model='mny'></Son1>-->
      </div>
    </template>
   
    components: { Son1 },
    data() { return { mny: 100 } },
    -----------------------------
    // 子组件
    <div>son1 {{value}}
        <button @cl='$emit("input", 500)'>更改父亲</button>
    </div>
    
    props: {
        value: { type: Number, default: 100 },
    }
复制代码

自定义v-model方式同步数据

在父组件中,直接使用v-model;在子组件的model中自定义v-model的value和input方法---如何自定义v-model?

    // 父组件
    <template>
      <div>Parent
        <Son1 v-model='mny'></Son1>-->
      </div>
    </template>
   
    components: { Son1 },
    data() { return { mny: 100 } },
    -----------------------------
    // 子组件
    <div>son1 {{mny}}
        <button @click='$emit("change", 500)'>更改父亲</button>
    </div>
    
    model: { // 自定义v-model
        prop: "mny", // 默认为value
        event: 'change' // 默认为input
    },
    props: {
        value: { type: Number, default: 100 },
    }
复制代码

.sync同步数据

在父组件中,使用.sync;在子组件中,触发update:money

.sync是:money和@update:money的语法糖,在子组件中触发的事件和绑定的事件一致--为什么可以使用.sync

    // 父组件
    <template>
      <div>Parent
        <!-- .sync等同于 <Son1 :money='mny' @update:money='val=>mny=val'></Son1> -->
        <Son1 :money.sync='mny' ></Son1> 
      </div>
    </template>
   
    components: { Son1 },
    data() { return { mny: 100 } },
    -----------------------------
    // 子组件
    <div>son1 {{money}}
        {{mny}}<button @click='$emit("update:money", 500)'>更改父亲</button>
    </div>
    
    props: {
        mny: { type: Number, default: 100 },
    }
复制代码

跨级组件间通信

通过属性逐级传递

provide+inject

在父组件中,指定父组件的name,通过provide将父组件暴露出去;子孙组件通过inject注入后可以直接使用父组件name.属性和方法

缺点:容易造成数据流的混乱;此时,子组件可以通过parent.money = 200直接改变父组件的数据,不推荐使用这种方法,建议调用父组件的方法改变数据
复制代码
    // 父组件
    <div>Parent <Son2></Son2> </div>
   
    name: 'parent',
    components: { Son2 },
    data() { return { mny: 100 } },
    provide() {
        return {
          parent: this, //将当前实例暴露出去
        }
    }
    -----------------------------
    // 子组件
    <div> <grandson></grandson> </div>
    -----------------------------
    // 孙组件
    <div> grandson {{parent.mny}} </div>
    
    inject: ['parent'], // 将父组件注入
复制代码

$parent|children

直接触发儿子或者父亲的方法

缺点:容易造成数据流的混乱,不好维护,尽量不使用;此时,子组件可以通过parent.money = 200直接改变父组件的数据,不推荐使用这种方法,建议调用父组件的方法改变数据
复制代码
    // 父组件
    <div>Parent <Son2  @eat='eat'></Son2> </div>
   
    name: 'parent',
    components: { Son2 },
    methods: {
        eat() { console.log('这是parent中的eat') }
    }
    -----------------------------
    // 子组件
    <div> <grandson></grandson> </div>
    -----------------------------
    // 孙组件
    <div>
        <button @click='$parent.$emit("eat")'>通过$parent触发son2事件</button>
    </div>
复制代码

$disatch:实现指定的派发功能

// 向上派发,触发指定组件的事件
Vue.prototype.$dispatch = function (eventName, componentName, value) {
  let parent = this.$parent;
  while(parent){
    if (parent.$options.name === componentName) {
      parent.$emit(eventName, value); // 没有绑定的触发没有意义
      return; // 一旦触发,不再继续向上查找
    }
    parent = parent.$parent;
  }
}

    // 父组件
    <div>Parent <Son2  @eat='eat'></Son2> </div>
    name: 'parent'
    -----------------------------
    // 子组件
    <div> <grandson></grandson> </div>
    name: 'son2'
    -----------------------------
    // 孙组件
    <div> <button @click='$dispatch("eat","parent", "饭")'>通过dispatch触发son2事件</button> </div>
    name: 'grandson'
复制代码

$broadcast:指定的广播功能

// 向下扩展
Vue.prototype.$broadcast = function (eventName, componentName, value) {
  let children = this.$children; // 是一个数组
  function broadcast(children) {
    for (let i = 0; i < children.length; i++) {
      let child = children[i];
      if (child.$options.name === componentName) {
        child.$emit(eventName, value)
        return;
      } else {
        if(child.$children) {
          broadcast(child.$children)
        }
      }
    }
  }
  broadcast(children)
}
复制代码

$attrs|listeners:是所有方法和属性的合集

1. 父级的属性如果在子组件中通过props使用,那么在$attrs中会减少;
2. $attrs是响应式的,父级改变,数据也会更新
3. 可以通过v-bind|v-on将所有属性或方法都传递给儿子组件;
4. 通过inheritAttrs: false将没有使用的属性不添加在dom元素上
5. 不能隔代传递
复制代码
    <Parent a='1' b='2' c='3' d='4'></Parent>
    -------------------------------------------------------------
    // Parent组件
    <div>
        Parent {{$attrs}}
        <Son v-bind="$attrs" @click='click' @mousedown='mousedown'></Son>
    </div>
    
    inheritAttrs: false,
    props: ['a', 'b'],
    methods: {
        click() {
          console.log('click')
        },
        mousedown() {
          console.log('mousedown')
        }
    }
    -------------------------------------------------------------
    // Son组件
    <div>
        Son {{$attrs}}
        <button @click="$listeners.click">点</button>
        <Grandson v-on="$listeners"></Grandson>
    </div>
    
    mounted() {
        console.log(this.$listeners)
    }
    -------------------------------------------------------------
    // Grandson组件
    <div>Grandson <button @click="$listeners.mousedown">ts点</button> </div>
复制代码

$refs:通过它获取目标组件,调用它的方法

在普通元素上获取dom元素;在v-for,获取的是一组dom|实例;在组件上,获取的是当前组件的实例
复制代码
    <myDialog ref='dialog'></myDialog>
    <button @click='change'>点我</button>
  
    methods: {
      change() {
        this.$refs.dialog.change();
        /* 
          ref:在普通元素上获取dom元素
              在v-for,获取的是一组dom、实例
              在组件上,获取的是当前组件的实例
         */
      }
    }
  }
复制代码

eventbus:发布订阅

1. 每个组件实例上都有\$on、\$emit、\$off;通过Vue.prototype.$bus = new Vue()可以让所有组件都能拿到\$bus,并创造公共实例,保证绑定事件和触发都是在同一个实例上
2. 注意在beforeDestroy上移除订阅
3. 子组件如何监听父组件的mounted?组件挂载:先挂载父组件,渲染子组件,--子mounted--父mounted;要实现子组件监听父组件的mounted,需要在子组件mounted时订阅一个监听事件,当父组件mounted时触发
复制代码
Vue.prototype.$bus = new Vue()
// 父组件
mounted(){
    this.$bus.$emit('监听事件', 1)
},

// 子组件
mounted(){
    this.$bus.$on('监听事件', (arg) => {
        console.log('父组件ok了', arg)
    })
}
beforeDestroy() {
    this.$bus.$off('监听事件')
 }
复制代码

缺点:可以实现任意组件间通信,但不好维护