资讯专栏INFORMATION COLUMN

vuejs组件通信精髓归纳

妤锋シ / 2760人阅读

摘要:组件的通信和和内置的通信手段一般有两种给元素或组件注册引用信息访问父子实例。有时候两个组件之间需要进行通信,但是它们彼此不是父子组件的关系。详情可参考参考组件之间种组件通信方式总结参考参考

组件的分类

常规页面组件,由 vue-router 产生的每个页面,它本质上也是一个组件(.vue),主要承载当前页面的 HTML 结构,会包含数据获取、数据整理、数据可视化等常规业务。

功能性抽象组件,不包含业务,独立、具体功能的基础组件,比如日期选择器、弹窗警告等。这类组件作为项目的基础控件,会被大量使用,因此组件的 API 进行过高强度的抽象,可以通过不同配置实现不同的功能。

业务组件,它不像第二类独立组件只包含某个功能,而是在业务中被多个页面复用的,它与独立组件的区别是,业务组件只在当前项目中会用到,不具有通用性,而且会包含一些业务,比如数据请求;而独立组件不含业务,在任何项目中都可以使用,功能单一,比如一个具有数据校验功能的输入框。

组件的关系 父子组件

父子关系即是组件 A 在它的模板中使用了组件 B,那么组件 A 就是父组件,组件 B 就是子组件。

// 注册一个子组件
Vue.component("child", {
    data: function(){
        return {
            text: "我是father的子组件!"
        }
    },
    template: "{{ text }}"
})
// 注册一个父组件
Vue.component("father", {
    template: "
" // 在模板中使用了child组件 })
兄弟组件

两个组件互不引用,则为兄弟组件。

Vue.component("brother1", {
    template: "
我是大哥
" }) Vue.component("brother2", { template: "
我是小弟
" })

使用组件的时候:

跨级组件

就是在父子关系中,中间跨了很多个层级

组件的构成

一个再复杂的组件,都是由三部分组成的:prop、event、slot,它们构成了 Vue.js 组件的 API。

属性 prop

prop 定义了这个组件有哪些可配置的属性,组件的核心功能也都是它来确定的。写通用组件时,props 最好用对象的写法,这样可以针对每个属性设置类型、默认值或自定义校验属性的值,这点在组件开发中很重要,然而很多人却忽视,直接使用 props 的数组用法,这样的组件往往是不严谨的。

插槽 slot

插槽 slot,它可以分发组件的内容。和 HTML 元素一样,我们经常需要向一个组件传递内容,像这样:


  Something bad happened.

可能会渲染出这样的东西:

Error!Something bad happended.

幸好,Vue 自定义的 元素让这变得非常简单:

Vue.component("alert-box", {
  template: `
    
Error!
` })

如你所见,我们只要在需要的地方加入插槽就行了——就这么简单!

自定义事件 event

两种写法:

在组件内部自定义事件event


通过 $emit,就可以触发自定义的事件 on-click ,在父级通过 @on-click 来监听:

用事件修饰符 .native直接在父级声明

所以上面的示例也可以这样写:

如果不写 .native 修饰符,那上面的 @click 就是自定义事件 click,而非原生事件 click,但我们在组件内只触发了 on-click 事件,而不是 click,所以直接写 @click 会监听不到。

组件的通信 ref和$parent和$children

Vue.js 内置的通信手段一般有两种:

ref:给元素或组件注册引用信息;

$parent / $children:访问父 / 子实例。

用 ref 来访问组件(部分代码省略):

// component-a
export default {
  data () {
    return {
      title: "Vue.js"
    }
  },
  methods: {
    sayHello () {
      window.alert("Hello");
    }
  }
}

$parent 和 $children 类似,也是基于当前上下文访问父组件或全部子组件的。
这两种方法的弊端是,无法在跨级或兄弟间通信,比如下面的结构:

// parent.vue


我们想在 component-a 中,访问到引用它的页面中(这里就是 parent.vue)的两个 component-b 组件,那这种情况下,是暂时无法实现的,后面会讲解到方法。

provide / inject

一种无依赖的组件通信方法:Vue.js 内置的 provide / inject 接口

provide / inject 是 Vue.js 2.2.0 版本后新增的 API,在文档中这样介绍 :
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。
provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。
假设有两个组件: A.vue 和 B.vue,B 是 A 的子组件:

// A.vue
export default {
  provide: {
    name: "Aresn"
  }
}

// B.vue
export default {
  inject: ["name"],
  mounted () {
    console.log(this.name);  // Aresn
  }
}

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

只要一个组件使用了 provide 向下提供数据,那其下所有的子组件都可以通过 inject 来注入,不管中间隔了多少代,而且可以注入多个来自不同父级提供的数据。需要注意的是,一旦注入了某个数据,那这个组件中就不能再声明 这个数据了,因为它已经被父级占有。

provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。然后有两种场景它不能很好的解决:

父组件向子组件(支持跨级)传递数据;

子组件向父组件(支持跨级)传递数据。

这种父子(含跨级)传递数据的通信方式,Vue.js 并没有提供原生的 API 来支持,下面介绍一种在父子组件间通信的方法 dispatch 和 broadcast。

$attrs和$listeners

如果父组件A下面有子组件B,组件B下面有组件C,这时如果组件A想传递数据给组件C怎么办呢? Vue 2.4开始提供了$attrs和$listeners来解决这个问题,能够让组件A之间传递消息给组件C。

Vue.component("C",{
        template:`
            
`, methods:{ passCData(val){ //触发父组件A中的事件 this.$emit("getCData",val) } } }) Vue.component("B",{ data(){ return { mymessage:this.message } }, template:`
`, props:["message"],//得到父组件传递过来的数据 methods:{ passData(val){ //触发父组件中的事件 this.$emit("getChildData",val) } } }) Vue.component("A",{ template:`

this is parent compoent!

`, data(){ return { message:"hello", messagec:"hello c" //传递给c组件的数据 } }, methods:{ getChildData(val){ console.log("这是来自B组件的数据") }, //执行C子组件触发的事件 getCData(val){ console.log("这是来自C组件的数据:"+val) } } }) var app=new Vue({ el:"#app", template:`
` })
派发与广播——自行实现 dispatch 和 broadcast 方法

要实现的 dispatch 和 broadcast 方法,将具有以下功能:
在子组件调用 dispatch 方法,向上级指定的组件实例(最近的)上触发自定义事件,并传递数据,且该上级组件已预先通过 $on 监听了这个事件;
相反,在父组件调用 broadcast 方法,向下级指定的组件实例(最近的)上触发自定义事件,并传递数据,且该下级组件已预先通过 $on 监听了这个事件。

// 部分代码省略
import Emitter from "../mixins/emitter.js"

export default {
  mixins: [ Emitter ],
  methods: {
    handleDispatch () {
      this.dispatch();  // ①
    },
    handleBroadcast () {
      this.broadcast();  // ②
    }
  }
}
 //emitter.js 的代码:
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);
    }
  }
};

因为是用作 mixins 导入,所以在 methods 里定义的 dispatch 和 broadcast 方法会被混合到组件里,自然就可以用 this.dispatch 和 this.broadcast 来使用。
这两个方法都接收了三个参数,第一个是组件的 name 值,用于向上或向下递归遍历来寻找对应的组件,第二个和第三个就是上文分析的自定义事件名称和要传递的数据。
可以看到,在 dispatch 里,通过 while 语句,不断向上遍历更新当前组件(即上下文为当前调用该方法的组件)的父组件实例(变量 parent 即为父组件实例),直到匹配到定义的 componentName 与某个上级组件的 name 选项一致时,结束循环,并在找到的组件实例上,调用 $emit 方法来触发自定义事件 eventName。broadcast 方法与之类似,只不过是向下遍历寻找。

来看一下具体的使用方法。有 A.vue 和 B.vue 两个组件,其中 B 是 A 的子组件,中间可能跨多级,在 A 中向 B 通信:



// B.vue
export default {
  name: "componentB",
  created () {
    this.$on("on-message", this.showMessage);
  },
  methods: {
    showMessage (text) {
      window.alert(text);
    }
  }
}

同理,如果是 B 向 A 通信,在 B 中调用 dispatch 方法,在 A 中使用 $on 监听事件即可。
以上就是自行实现的 dispatch 和 broadcast 方法。

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

它适用于以下场景:

由一个组件,向上找到最近的指定组件;

由一个组件,向上找到所有的指定组件;

由一个组件,向下找到最近的指定组件;

由一个组件,向下找到所有指定的组件;

由一个组件,找到指定组件的兄弟组件。

5 个不同的场景,对应 5 个不同的函数,实现原理也大同小异。

向上找到最近的指定组件——findComponentUpward
// 由一个组件,向上找到最近的指定组件
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 };

比如下面的示例,有组件 A 和组件 B,A 是 B 的父组件,在 B 中获取和调用 A 中的数据和方法:





向上找到所有的指定组件——findComponentsUpward
// 由一个组件,向上找到所有的指定组件
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 };
向下找到最近的指定组件——findComponentDownward
// 由一个组件,向下找到最近的指定组件
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 };
向下找到所有指定的组件——findComponentsDownward
// 由一个组件,向下找到所有指定的组件
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 };
找到指定组件的兄弟组件——findBrothersComponents
// 由一个组件,找到指定组件的兄弟组件
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 };

相比其它 4 个函数,findBrothersComponents 多了一个参数 exceptMe,是否把本身除外,默认是 true。寻找兄弟组件的方法,是先获取 context.$parent.$children,也就是父组件的全部子组件,这里面当前包含了本身,所有也会有第三个参数 exceptMe。Vue.js 在渲染组件时,都会给每个组件加一个内置的属性 _uid,这个 _uid 是不会重复的,借此我们可以从一系列兄弟组件中把自己排除掉。

Event Bus

有时候两个组件之间需要进行通信,但是它们彼此不是父子组件的关系。在一些简单场景,你可以使用一个空的 Vue 实例作为一个事件总线中心(central event bus):

 //中央事件总线
    var bus=new Vue();

    var app=new Vue({
        el:"#app",
        template:`
            
` }) // 在组件 brother1 的 methods 方法中触发事件 bus.$emit("say-hello", "world") // 在组件 brother2 的 created 钩子函数中监听事件 bus.$on("say-hello", function (arg) { console.log("hello " + arg); // hello world })
vuex处理组件之间的数据交互

如果业务逻辑复杂,很多组件之间需要同时处理一些公共的数据,这个时候才有上面这一些方法可能不利于项目的维护,vuex的做法就是将这一些公共的数据抽离出来,然后其他组件就可以对这个公共数据进行读写操作,这样达到了解耦的目的。 详情可参考:https://vuex.vuejs.org/zh-cn/

参考 vue组件之间8种组件通信方式总结
参考 https://github.com/iview/ivie...
参考 https://github.com/iview/ivie...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/101958.html

相关文章

  • Vue 组件通信方式

    摘要:本身提供哪几种通信方式首先灵感源于,支持双向绑定,本质还是单向数据流。跟一样,组件间最基本的数据流是通过向子组件传递数据。但是在却很少使用,因为组件可以自定义事件,即后面的组件间通信方式其实就是订阅发布模式。 例子是在 jsrun.net 平台编写,不支持移动端平台,所以本文建议在 PC 端进行阅读。 Vue 是数据驱动的视图框架,那么组件间的数据通信是必然的事情,那么组件间如何进行数...

    hss01248 评论0 收藏0
  • VUE组件化开发的精髓

    摘要:开篇的简单介绍和演示的开发精髓组件组件的三个组件之间的通信方式实例讲解銖宝益帮助中心前端组件开篇的简单介绍和演示发布于年,是一个渐进式的框架,同时也是一个轻量级的框架,它只关心数据,从而让开发者不用过多的关注的改变和操作,的作者为尤雨溪, 开篇:vue.js的简单介绍和演示 vue的开发精髓-组件 vue组件的三个API:prop、event、slot 组件之间的通信方式 实例讲解:銖宝益帮...

    e10101 评论0 收藏0
  • vue采用EventBus实现跨组件通信及注意事项

    摘要:挂载到添加文件第一个参数是事件对象,第二个参数是接收到消息信息,可以是任意类型事件订阅监听当前实例上的自定义事件。取消事件订阅,移除自定义事件监听器。 EventBus EventBus是一种发布/订阅事件设计模式的实践。在vue中适用于跨组件简单通信,不适应用于复杂场景多组件高频率通信,类似购物车等场景状态管理建议采用vuex。 挂载EventBus到vue.prototype 添加...

    Panda 评论0 收藏0
  • Vue组件之间通信的八种方式

    摘要:使用也有很长一段时间但是一直以来都没对其组件之间的通信做一个总结这次就借此总结一下。引用信息将会注册在父组件的对象上。 使用Vue也有很长一段时间,但是一直以来都没对其组件之间的通信做一个总结,这次就借此总结一下。 父子组件之间的通信 1)props和$emit 父组件通过props将数据下发给props,子组件通过$emit来触发自定义事件来通知父组件进行相应的操作 具体代码如下: ...

    klinson 评论0 收藏0
  • Reactjs vs. Vuejs

    摘要:上图是二月份前端框架排名,位居第一,排名第三。我们认为前端模板和组件代码是紧密相连的。直到最近看了文档,才发现另有蹊跷。 欢迎大家关注腾讯云技术社区-segmentfault官方主页,我们将持续在博客园为大家推荐技术精品文章哦~ 纪俊,从事Web前端开发工作,2016年加入腾讯OMG广告平台产品部,喜欢研究前端技术框架。 这里要讨论的话题,不是前端框架哪家强,因为在 Vue 官网就已经...

    AaronYuan 评论0 收藏0

发表评论

0条评论

妤锋シ

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<