# 组件间通信
## 常见的组件间通讯场景
1. 父组件向子组件传递数据（$dispatch）
    （父组件向子组件传递数据）
    最常见的组件间通讯场景就是父级组件向子级组件传递数据。这通常有两种情况。
    父级组件向直接子组件传递数据（图）
    如图，root组件向B组件传递数据，在这种情况下，通常有两种方式传递数据，一是直接在组件初始化时直接向子组件属性中传入数据。可以采用如下的方式向子组件中传入数据：
    var BlockGroup = Van.component({
        data: {
            h1: 0,
            h2: 0,
            x: 300
        },
        components: [
            Block.newInstance({
                y: this.x,
            })
        ]
    });
    在上述代码中，父组件BlockGroup在子组件Block初始化时将内部的x属性传入。
    除此之外，父组件直接向子组件传递数据时，通常可以将数据的绑定关系也同时传入。当父组件的该属性改变时，子组件的相关属性也同时更新
    （图）
    
    这种方式使得父组件与直接组件的关联更加密切，让父组件可以更加方便地控制子组件的展示情况，同时也更加利于组件的抽象。
    第二种方式，主要针对于组件运行中向子组件传输数据，使用事件派发的方式向组件传输数据，调用方式如下：
    var Parent = Van.component({
        // ...省略部分代码
        render: function() {
            this.$ctx.strokeRect(this.x, this.y, this.width, this.height);
            // 向子组件传递数据
            this.$dispatch("sing",this.name);
        }
    });

    var Child =  Van.component({
        // ...省略部分代码
        handler: {
            sing: function(data) {
                console.log(data);
            }
        }
    });

    在上述代码中，我们可以看到这种方式主要包含两个步骤：
    1. 组件注册事件接收器并实现
    2. 父组件触发事件
    首先需要在子组件中注册事件接收器，父组件触发事件后，触发的事件通过事件分发（Event dispatch）到达子组件,子组件在根据handerName找到事件处理器并处理事件。在整个过程中，触发事件和处理事件均可传入相关参数，使得在处理事件的过程中可以传输数据。
    父级组件向非直接子组件传递数据（图）
    当父级组件向非直接组件传递数据时，由于与非直接组件并没有直接的关联，我们无法在初始化时将数据传入，不过我们仍然可以使用基于事件派发的方式传递数据。
2. 子组件向父组件传递数据($emit)
    （子组件向父级组件传递数据）（图）
    子组件向父组件传递数据也是一种常见的组件间通讯场景。
    Vanjs采用事件冒泡的方式实现了事件,示例代码如下：
    var Child = Van.component({
        // ...省略部分代码
        render: function() {
            this.$ctx.strokeRect(this.x, this.y, this.width, this.height);
            // 向子组件传递数据
            this.$emit("sing",this.name);
        }
    });

    var Parent =  Van.component({
        // ...省略部分代码
        handler: {
            sing: function(data) {
                console.log(data);
            }
        }
    });

    在上述代码中，我们可以看到这种方式主要包含两个步骤：
    1. 组件注册事件接收器并实现
    2. 子组件触发事件冒泡
    首先需要在父组件中注册事件处理器，子组件触发事件后，触发的事件冒泡（Event Bubble）层层冒泡至目标父组件,父组件在根据handerName找到事件处理器并处理事件。
    （冒泡示意图）
3. 子组件向组件树中的任意组件传递数据
    子组件向所有组件树种的其他组件传递数据也是一种常见的场景，我们通常称这种方式为事件广播（Event Broadcast）
    在Vanjs中，采用了事件冒泡和事件分发组合的方式实现广播。示例代码如下：
    var Child = Van.component({
        // ...省略部分代码
        render: function() {
            this.$ctx.strokeRect(this.x, this.y, this.width, this.height);
            // 向子组件传递数据
            this.$broadcast("sing",this.name);
        }
    });

    var Parent =  Van.component({
        // ...省略部分代码
        handler: {
            sing: function(data) {
                console.log(data);
            }
        }
    });
    (事件广播示意图)
    从图中我们可以看到，事件广播分为两个过程：
    1. 事件冒泡：事件通过在父子组件间不断地冒泡直到根组件（root component）
    2. 事件分发：根组件触发事件派发，向所有子级组件派发事件
    通过整合以上的两个过程，我们可以清晰简洁地解决组件向所有其他组件广播的问题。
## Van组件间通讯实现
在Vanjs的组件通讯中，提供的组件通讯相关的api如下：
Van.prototype.$emit | function :触发消息传输事件并传输数据，触发的事件会不停向上冒泡。
Van.prototype.$dispatch | function: 触发消息传输事件并传输数据，触发的事件会不断向所有组件广播。
Van.prototype.$broadcast | function: 触发消息传输事件并传输数据，触发的事件会想所有组件广播。
同时，在组件初始化时，也可以通过传入初始化参数的方式注册消息事件接收器，主要的属性：
handlers: Object
在组件的执行过程中，也可以通过相关的api实现消息接收器的注册，主要的api如下：
Van.prototype.$registHandler: function : 注册消息处理器
Van.prototype.$unregistHandler: function ： 取消消息处理器

### 整体实现
Vanjs的消息通信的实现方式是基于事件触发式的。在每个组件上都可以注册若干个事件处理器，当有消息事件到达该组件时，系统会根据消息事件的名称（handlerName）判断是否对该事件进行处理。
触发的事件主要分为三种类型：
* 冒泡事件： 使用$emit触发,所有该组件的上级组件都会接收到该消息事件。
* 派发事件： 使用$dispatch触发，所有该组件的下级组件都会接收到该消息事件。
* 广播事件： 使用$broadcast触发，组件树内所有组件都会受到该消息事件。

### 消息事件注册的实现
消息处理器注册的方式主要有两种方式：初始化时注册和运行时注册，其目的都是在该组件对象上添加或者删除注册事件。
以下为初始化注册时的相关部分实现：
/src/internal/init.js

``` javascript
Van.prototype._init = function(options) {
    // ...省略部分代码

    // 初始化handlers
    // 先使用参数中的events直接覆盖
    // 如果有其他不能覆盖的情况，再做修改
    if (options.handlers) {
      this._handler = options.handler;
    }

};
```
组件内部使用_handlers存放所有的注册事件，初始化时间注册参数中的handler直接复制到_handlers中

同时，在运行时提供了$registHandler和$unregistHandler来注册和删除事件接收器，以下为具体实现：

### $registHandler
/src/api/event.js
``` javascript
  /**
   * 注册事件
   * 事件执行后，如果返回值为true，则继续冒泡
   * @param {string} handlerName : 需处理的事件名称
   * @param {object|string|boolean} func : 事件处理函数
   */
  Van.prototype.$registHandler = function(handlerName, func) {

    // 检测是否已包含该事件名称
    if (this._handlers.hasOwnProperty(handlerName)) {
      tip('handler注册失败:该handler名称已存在', 'error');
      return false;
    }

    // 检测func类型
    if (typeof func !== 'function') {
      tip('handler注册失败:handler处理需为function', 'error');
      return false;
    }

    // 注册事件
    this._handlers[handlerName] = func;
    return true;
  };
```
### $unregistHandler
``` javascript
/**
* 取消已注册事件
* @param {string} handlerName : 需处理的事件名称
*/
Van.prototype.$unregistHandler = function(handlerName) {

// 判断是否已注册该事件
if (!this._handlers.hasOwnProperty(handlerName)) {
    tip('事件取消注册失败:未找到名为' + handlerName + '的事件', 'warn');
    return false;
}

delete this._handlers[handlerName];
return true;
};
```

### 几种事件传输机制的实现

#### 冒泡事件
当调用$emit后会触发冒泡事件，冒泡事件的事件代码如下：
``` javascript
/**
* 触发事件，事件不断向上级组件冒泡
* @param {string} handlerName : 事件名称
* @param {object|string|boolean} data : 传输数据
*/
Van.prototype.$emit = function(handlerName, data) {

// 如果为根组件，则直接退出
if (this.$isRoot) {
    return;
}
// 直接调用父级组件的_handleEmit
this.$parent._handleEmit(handlerName, data);
};
```
在$emit的内部使用_handleEmit来处理冒泡事件
``` javascript
/**
* 解析事件
* 事件执行后，如果返回值为true，则继续冒泡
* @param {string} handlerName : 需处理的事件名称
* @param {object|string|boolean} data : 传递的数据对象
*/
Van.prototype._handleEmit = function(handlerName, data) {
let func = this._handler[handlerName];

// 判断自身是否有处理该事件的函数
if (!func) {

    // 如果为根组件，则不会有父级组件，故直接退出
    if (this.$isRoot) {
    return;
    }
    return this.$parent._handleEmit(handlerName, data);
} else {
        let flag = func.call(this, data);

        // 如果返回值为true,则继续冒泡
        if (flag) {

        // 如果为根组件，则不会有父级组件，故直接退出
        if (this.$isRoot) {
            return;
        }
        this.$parent._handleEmit(handlerName, data);
        } else {
        return false;
        }
    }
};
```
在_handleEmit中，首先会判断自身是否有出来该事件的函数，如果有则执行。接着判断是否为根组件，如果不是的话则使用$parent属性找到父级组件并触发父级组件的_handleEmit方法。这样的话该事件就能通过组件树层层向上传递。

#### 派发事件
当组件内部调用$dispatch会触发派发事件，派发事件的实现代码如下：
``` javascript
/**
* 派发事件，事件不断向下级派发
* @param {string} handlerName : 事件名称
* @param {object|string|boolean} data : 传输数据
*/
Van.prototype.$dispatch = function(handlerName, data) {
let components = this.$components;
for (let key in components) {
    let component = components[key];
    component._handleDispatch(handlerName, data);
}
};
```
在$emit的内部使用_handleEmit来处理冒泡事件，_handleEmit的实现代码如下：
``` javascript
/**
* 内部广播事件，事件不断向下级广播
* @param {string} handlerName : 事件名称
* @param {object|string|boolean} func : 传输数据
*/
Van.prototype._handleDispatch = function(handlerName, data) {
    let func = this._handler[handlerName];

    // 判断自身是否有处理该事件的函数
    if (!func) {

        // 直接向子组件派发
        for (let key in this.$components) {
        this.$components[key]._handleDispatch(handlerName, data);
        }

    } else {
        let flag = func.call(this, data);

        // 如果返回值为true,则继续派发
        if (flag) {
        for (let key in this.$components) {
            this.$components[key]._handleDispatch(handlerName, data);
        }
        }
    }
};
```
在_handleEmit中，首先会判断自身是否有出来该事件的函数，如果有则执行。接着判断是否为根组件，如果不是的话则使用$components属性找到子组件并触发子组件的的_handleDispatch方法。这样的话该事件就能通过组件树层层向下传递。

### 广播事件
当组件内部调用$broadcast会触发广播事件，广播事件的实现代码如下：
``` javascript
/**
* 广播事件
* 事件向组件树内所有组件广播
* @param {string} handlerName : 需处理的事件名称
* @param {object|string|boolean} data : 传递的数据对象
*/
Van.prototype.$broadcast = function(handlerName, data) {
    this._handleBroadcast(handlerName, data);
};

Van.prototype._handleBroadcast = function(handlerName, data) {

    // 判断是否为根组件
    if (this.$isRoot) {
        this.$dispatch(handlerName, data);
    } else {
        this.$parent._handleBroadcast(handlerName, data);
    }
};
```
$broadcast通过内部调用_handleBroadcast实现广播，在_handleBroadcast中，首先判断是否为根组件，如果不是的话，则调用父组件的_handleBroadcast方法继续向上传递。如果是根组件的话，则直接触发$dispatch事件，向所有组件发送一个派发事件。