组件通信-进阶

基础

父子组件

  • 父组件引用子组件
  • 父组件给子组件传值(props)
  • 子组件触发父组件方法($emit)
  • props双向数据绑定(.sync)
  • 父组件调用子组件方法和值(ref)
  • 子组件使用父组件的值和方法($parent)

兄弟组件

  • 子——>父——>子(通过上面的父子组件传递的方法)
  • vuex
  • event-bus

组件通信-基础 文章地址

进阶(跨级通信)

##1. provide / inject

这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。只能向下传递

image-20200721103732135
// index.vue
export default {
  provide: {
    name: 'Aresn'
  }
}

// C.vue
export default {
  inject: ['name']
}

provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。

所以,上面 index.vue 的 name 如果改变了,C.vue 的 this.name 是不会改变的,仍然是 Aresn。

//对象是,可响应的。
export default {
  data() {
    return {
      obj: {
        name: "Aresn",
        age: 12
      }
    };
  },
  provide() {
    return {
      obj: this.obj
    };
  }
}

2020-07-18 14-49-02.2020-07-18 14_50_24

2. $attrs/$listeners

image-20200721142542421

3.派发与广播(dispatch/broadcast)

通过$on$emit实现向上派发(dispatch)和向下广播(broadcast)

  • 在子组件调用 dispatch 方法,向上级指定的组件实例(最近的)上触发自定义事件,并传递数据,且该上级组件已预先通过 $on 监听了这个事件;
  • 相反,在父组件调用 broadcast 方法,向下级指定的组件实例(最近的)上触发自定义事件,并传递数据,且该下级组件已预先通过 $on 监听了这个事件。
image-20200721114632158

具体方法如下,写入到jsemitter.js)文件中,通过mixins引入

function broadcast(componentName, eventName, params) {
  this.$children.forEach(child => {
    const name = child.$options.name;

    if (name === componentName) {
      child.$emit.apply(child, [eventName].concat(params));
    } else {
      broadcast.apply(child, [componentName, eventName].concat([params]));
    }
  });
}
export default {
  methods: {
    dispatch(componentName, eventName, params) {
      let parent = this.$parent || this.$root;
      let name = parent.$options.name;

      while (parent && (!name || name !== componentName)) {
        parent = parent.$parent;

        if (parent) {
          name = parent.$options.name;
        }
      }
      if (parent) {
        parent.$emit.apply(parent, [eventName].concat(params));
      }
    },
    broadcast(componentName, eventName, params) {
      broadcast.call(this, componentName, eventName, params);
    }
  }
};

参数

  • 组件的 name
  • 自定义事件名称
  • 要传递的数据

使用方法

A.vueB.vue 两个组件,其中 B 是 A 的子组件,中间可能跨多级,在 A 中向 B 通信:

<!-- A.vue -->
<template>
	<button @click="handleClick">触发事件</button>
</template>
<script>
  import Emitter from '../mixins/emitter.js';
  
  export default {
    name: 'componentA',
    mixins: [ Emitter ],
    methods: {
      handleClick () {
        this.broadcast('componentB', 'on-message', 'Hello Vue.js');
      }
    }
  }
</script>
// B.vue
export default {
  name: 'componentB',
  created () {
    this.$on('on-message', this.showMessage);
  },
  methods: {
    showMessage (text) {
      window.alert(text);
    }
  }
}

2020-07-21 11-06-11.2020-07-21 11_15_04

4. 找到任意组件实例——findComponents 系列方法

它适用于以下场景:

  • 由一个组件,向上找到最近的指定组件;
  • 由一个组件,向上找到所有的指定组件;
  • 由一个组件,向下找到最近的指定组件;
  • 由一个组件,向下找到所有指定的组件;
  • 由一个组件,找到指定组件的兄弟组件。
// 由一个组件,向上找到最近的指定组件
function findComponentUpward (context, componentName) {
    let parent = context.$parent;
    let name = parent.$options.name;

    while (parent && (!name || [componentName].indexOf(name) < 0)) {
        parent = parent.$parent;
        if (parent) name = parent.$options.name;
    }
    return parent;
}
export { findComponentUpward };

// 由一个组件,向上找到所有的指定组件
function findComponentsUpward (context, componentName) {
    let parents = [];
    const parent = context.$parent;

    if (parent) {
        if (parent.$options.name === componentName) parents.push(parent);
        return parents.concat(findComponentsUpward(parent, componentName));
    } else {
        return [];
    }
}
export { findComponentsUpward };

// 由一个组件,向下找到最近的指定组件
function findComponentDownward (context, componentName) {
    const childrens = context.$children;
    let children = null;

    if (childrens.length) {
        for (const child of childrens) {
            const name = child.$options.name;

            if (name === componentName) {
                children = child;
                break;
            } else {
                children = findComponentDownward(child, componentName);
                if (children) break;
            }
        }
    }
    return children;
}
export { findComponentDownward };

// 由一个组件,向下找到所有指定的组件
function findComponentsDownward (context, componentName) {
    return context.$children.reduce((components, child) => {
        if (child.$options.name === componentName) components.push(child);
        const foundChilds = findComponentsDownward(child, componentName);
        return components.concat(foundChilds);
    }, []);
}
export { findComponentsDownward };

// 由一个组件,找到指定组件的兄弟组件
function findBrothersComponents (context, componentName, exceptMe = true) {
    let res = context.$parent.$children.filter(item => {
        return item.$options.name === componentName;
    });
    let index = res.findIndex(item => item._uid === context._uid);
    if (exceptMe) res.splice(index, 1);
    return res;
}
export { findBrothersComponents };

5.跨层级组件实例——provide / inject

  1. npm install vue-ref || yarn add vue-ref 安装vue-ref插件
  2. 导入import ref from "vue-ref"
  3. 使用插件Vue.use(ref, { name: "ant-ref" });name是给插件起名

使用方法

//使用`provide` 在根组件提供数据 
provide() {
  return {
    //主动通知 将组件实例绑定在根组件上
    setChildrenRef: (name, ref) => {
      this[name] = ref;
    },
    //主动获取 获取绑定的组件
    getChildrenRef: name => {
      return this[name];
    },
    // 获取根组件
    getRef: () => {
      return this;
    }
  }
}
// 使用`inject` 在子组件中注入数据
inject: {
  setChildrenRef: {
    default: () => {}
  },
  getParentRef: {
    from: "getRef",
    default: () => {}
  },
  getParentChildrenRef: {
    from: "getChildrenRef",
    default: () => {}
  }
}

//使用指令注册子组件
<ChildrenH v-ant-ref="ref => setChildrenRef('childrenH', ref)" />
//使用指令注册DOM元素
<h3 v-ant-ref="ref => setChildrenRef('childrenE', ref)">E 结点</h3>

vue-ref插件源码

"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = {
  install: function install(Vue) {
    var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
    var directiveName = options.name || 'ref';
    console.log(arguments)
    Vue.directive(directiveName, {
      bind: function bind(el, binding, vnode) {
        //自定义指令传入值 是函数, 在这里执行 传入组件实例
        binding.value(vnode.componentInstance || el, vnode.key); //vnode.key 是使用插件时起的名称
      },
      update: function update(el, binding, vnode, oldVnode) {
        if (oldVnode.data && oldVnode.data.directives) {
          var oldBinding = oldVnode.data.directives.find(function (directive) {
            var name = directive.name;
            return name === directiveName;
          });
          if (oldBinding && oldBinding.value !== binding.value) {
            oldBinding && oldBinding.value(null, oldVnode.key);
            // 如果指令绑定的值有变化,则更新 组件实例
            binding.value(vnode.componentInstance || el, vnode.key);
            return;
          }
        }
        // Should not have this situation
        if (vnode.componentInstance !== oldVnode.componentInstance || vnode.elm !== oldVnode.elm) {
          binding.value(vnode.componentInstance || el, vnode.key);
        }
      },
      unbind: function unbind(el, binding, vnode) {
        binding.value(null, vnode.key);
      }
    });
  }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值