# 组件通信

# 跨层级传值

组件传值,除了我们常用的 props、$emit / $on、vuex、Bus,还有一个不常用但在封装高级组件时会很实用的方法: provide/inject

以el-button组件中size大小为例

// form.vue
provide() {
  return {
    elForm: this
  };
},
props: {
  size: String,
}
// button.vue
inject: {
  elForm: {
    default: ''
  },
  elFormItem: {
    default: ''
  }
},
props: {
  size: String,
},
computed: {
  _elFormItemSize() {
    return (this.elFormItem || {}).elFormItemSize;
  },
  buttonSize() {
    // size优先级 组件本身 > el-item > el-form
    return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
  }
}

# 跨层级事件

复杂组件往往层级很深,需要依赖于跨层级通信

# 父子交流,勉强谈得来

<component-a @say="say"></component-a>
const componentA = {
  methods: {
    handleClick () {
      this.$emit('say', '大哥大嫂过年好')
    }
  },
  template: `<button v-on:click="handleClick">按钮</button>`
}

export default {
  components: {
    componentA
  },
  methods: {
    say (data) {
      console.log(`小日本咆哮道: ${data}`)
    }
  }
}

# 隔代交流,完全没有话题

<wrap @say="say">
  <component-a></component-a>
</wrap>
const wrap = {
  template: `<div><slot></slot></div>`
}
const componentA = {
  methods: {
    handleClick () {
      this.$emit('say', '大哥大嫂过年好')
    }
  },
  template: `<button v-on:click="handleClick">按钮</button>`
}

export default {
  components: {,
    wrap,
    componentA
  },
  methods: {
    say (data) {
      console.log(`小日本咆哮道: ${data}`)
    }
  }
}

# 沉浸在一个人的小宇宙

<button @click="onClick">自嗨</button>
export default {
  created () {
    this.$on('say', data => {
      console.log(`小日本咆哮道: ${data}`)
    })
  },
  methods: {
    onClick () {
      this.$emit('say', '大哥大嫂过年好')
    }
  }
}

# 换位思考,话题就多了

<wrap @say="say">
  <component-a></component-a>
</wrap>
const wrap = {
  template: `<div><slot></slot></div>`
}

const componentA = {
  name: 'component-a',
  methods: {
    handleClick () {
      this.$parent.$emit('say', '大哥大嫂过年好')
    }
  },
  template: `<button v-on:click="handleClick">按钮</button>`
}

export default {
  components: {
    wrap: wrap,
    'component-a': componentA
  },
  methods: {
    say (data) {
      console.log(`小日本咆哮道: ${data}`)
    }
  }
}

# 祖祖辈辈, 一起浪

给组件取个名,子组件找祖先组件时,不用关心它的层级,根据 componentName 向上循环查找即可

<wrap>
  <wrap>
    <component-a></component-a>
  </wrap>
</wrap>
const wrap = {
  template: `<div><slot></slot></div>`
}

const componentA = {
  name: 'component-a',
  computed: {
    parent () {
      let parent = this.$parent;
      let parentName = parent.$options.componentName;
      while (parentName !== 'test') {
        parent = parent.$parent;
        parentName = parent.$options.componentName;
      }
      return parent;
    }
  },
  methods: {
    handleClick () {
      this.parent.$emit('say', '大哥大嫂过年好')
    }
  },
  template: `<button v-on:click="handleClick">按钮</button>`
}

export default {
  componentName: 'test',
  components: {
    wrap: wrap,
    'component-a': componentA
  },
  created () {
    this.$on('say', data => {
      console.log(`小日本咆哮道: ${data}`)
    })
  }
}

# element 跨层级通信实现

原理: 通过循环或递归寻找指定的组件,然后在组件本身$emit事件并传递参数

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

    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) {
      var parent = this.$parent || this.$root;
      var name = parent.$options.componentName;

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

        if (parent) {
          name = parent.$options.componentName;
        }
      }
      if (parent) {
        parent.$emit.apply(parent, [eventName].concat(params));
      }
    },
    broadcast(componentName, eventName, params) {
      broadcast.call(this, componentName, eventName, params);
    }
  }
}
Last Updated: 7/16/2020, 4:28:33 PM