实现组件之间的通信 - (发布-订阅模式)

本文介绍

本文主要讲解JavaScript中的发布-订阅模式,也可以叫做观察者模式,个人觉得前者叫法更适合从代码的角度来理解。

文章最终目的是实现组件(模块)之间的通信,其中会额外实现一下组件的生命周期功能。因为目前某几大框架实在是太火了,所以最近写的文章,都围绕着这些框架所用的知识点来写。

本文代码风格: ES5

内容

发布-订阅模式

在讲组件间通信前,我们先从其实现的基础讲起,那就是发布-订阅模式

下面我们就来实现一下发布-订阅的功能,那么实现之前,先来分析下该功能的需求:

注意:需求有很多种,下面只是其中一种,这种会比较贴近我们最终的需求(组件通信)

  1. 有一个发布者
  2. 发布者拥有一个记事本,上面记录着与订阅者间约定好事情和该事情的类型(事情就是函数)
  3. 发布者有一个广播的方法,该方法接受一个类型参数。每次广播时,就会把记事本上该类型的事情都执行一遍
  4. 发布者可以在记事本中添加某件事情,并且要指定该事情的类型
  5. 发布者也可以从记事本删除掉某件事情(需要知道该事情的类型)

注意我上面加粗的文字!

搞清楚需求后,就开始写代码:

// 1.发布者
var publisher = {};
// 2.拥有一个笔记本,这里使用对象而不使用数组
// 因为 "类型": "事情",属于键值对
publisher.notebook = {}
// 3.拥有广播方法,会将类型的事情都执行一遍
publisher.broadcast = function (type) {
    //取出事情。一个类型,可以有多件事情
    // funArray是数组,里面装的是函数
    var funArray = publisher.notebook[type];
    // 把所有事情执行一遍(事情就是函数)
    for (var i = 0; i < funArray.length; i++) {
        funArray[i]();
    }
}

// 4.记事本可以添加事情并指定类型
publisher.addFun = function (type, fun) {
    // 为了代码更容易阅读,没做容错处理,前面和后面也是
    publisher.notebook[type].push(fun)
}

// 5.记事本可以删除事情
publisher.removeFun = function (type, fun) {
    var funArray = publisher.notebook[type];
    for (var i = 0; i < funArray.length; i++) {
        if (fun === item) {
            funArray.splice(i, 1)
        }
    }
}

功能写完了,来测试一下效果:

// 订阅者
var user = {
    // 订阅者要约的事情
    yue: function () {
        console.log('正在执行要约的事');
    }
}
// 添加到发布者的笔记本,随意给的类型
publisher.addFun('type1' , user.yue)
// 广播 
publisher.broadcast('type1')
成功打印

上面就是发布-订阅模式,已成功实现。其实同一种设计模式,可以有很多不同的用法,但是其核心思想不变。

组件之间的通信

上面的发布-订阅模式中,有一个对象充当发布者,有一个对象充当订阅者。但是组件间的通信好像完全不是这么回事,因为似乎每个组件(对象)即可以充当发布者,也可以充当订阅者。

没错,按照上面的例子,只要你为订阅者user也添加发布者的功能,那么这两个对象就能实现通信了。

不过这样做的话实在太麻烦了,哪怕引用mixin的概念来实现,也是浪费性能。

所以这里再引入一个中介者的概念,这个中介者就是刚才的发布者publisher,它是个全局对象,在哪里都可以引用。(在框架中,一般为类的静态属性,如Vue.xxx,React.xxx)

组件通过中介者(全局发布者),来注册事件(约定事情),也可以主动通知(调用)中介者,让其执行某类型的事情。

基于上面代码,我们来实现一下两个组件之间的通信:

如果对下面代码阅读比较吃力的话(我觉得我注释写的挺好的,你应该看得懂),可以看下我另一篇关于生命周期的文章,会帮助你更好的理解组件大概是怎么一回事。

// 模拟一个Vue React那样的组件
function Component (option) {
    // 把传递进来的option对象的属性和方法揽到自己身上
    for (var key in option) {
        this[key] = option[key];
    }
    // 开始执行生命周期方法
    this.init();
}
// 定义一些生命周期方法,并让其按顺序进行
Component.prototype = {
    constructor: Component,
    init: function () {
        // 组件渲染
        this.render();
        // 组件挂载完毕
        this.componentDidMount();
    },
    // 默认有此方法,但render方法一定要自己写,不然报错
    componentDidMount: function () {

    }
}

// 现在我们的目的是:创建两个组件,让两个组件实现通信(传输数据),下面是component1给component2传递数据

// 由于涉及到传递数据,这里我们小幅度修改publisher的广播方法,让它多一个data参数

// 其他均不变,多了一个data参数
publisher.broadcast = function (type, data) {
   var funArray = publisher.notebook[type];
   for (var i = 0; i < funArray.length; i++) {
        // 执行函数时,传递data
        funArray[i](data);
    }
}

// 组件1
var component1 =  new Component({
    // 该组件的数据
    data: {
        passValue: '我是component1传递过来的值'
    },
    // 重写render
    render: function () {
      
    },
     // 自定义的一个方法,该方法可以通知中介者执行广播
    _emit: function (type) {
        publisher.broadcast(type, this.data.passValue);
    }

})

var component2 = new Component({
        data: {
            
        },
        render: function () {
        
        },
    // 组件挂载完后,注册事件
    componentDidMount: function () {
        // 利用中介者注册事件
        publisher.addFun('log', function (data) {
            console.log('我是组件2,接受到的数据是:' + data);
        })
    }
    
})
// 这里手动触发,在框架中,一般会有按钮,点击即可触发this._emit('log')
component1._emit('log');
组件之间成功通信

代码建议复制份自己跑一下,会有助于你理解组件的原理。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容