# Van.js组件化设计
## 组件化设计
### 在Canvas框架的设计中，图形组件化设计是非常重要的一个环节。通过良好的组件化方案的设计，既可以使得用户创建、操作组件时更为方便、快捷，同时也可以有效提升代码的重用度，减小开发复杂度、提升代码的可读性和可维护性，提升开发的效率。Vanjs在进行组件化设计主要基于以下几个原则
1. 组件是对逻辑的封装，不限于图形元素。
2. 组件具备单个可移植性，即“随加载随用”，不需要为其准备复杂的基础条件（如引入样式、引入框架等）。
3. 组件的定义应该是声明式的，而非命令式的。
### 在第一个原则中提到：组件是对逻辑的封装，不限于图形元素。也就是说我们可以将一个纯粹的图形抽象为一个组件，同时也可以为该图形组件添加复杂的内部业务逻辑，甚至封装一个只有业务逻辑的组件，即我们可以把if做成组件、把一个倒计时做成组件、把一段动画做成组件、把路由做成组件、把数据架构做成组件。组件的内部业务逻辑、生命周期也应当完全由组件控制，如果需要由外部控制的话，应该以接口形式或者消息传递的方式进行控制。
### 在第二个原则中提到：组件具备单个可移植性，即“随加载随用”。组件的单个可移植性意思是要尽量减少组件的外部依赖，使得组件在引入是不需要为其准备复杂的基础条件（如引入样式、引入框架等）。
### 在第三个原则中提到：组件的定义应该是声明式的，而非命令式的。传统的Canvas操作使用基于函数调用的，示例代码如下：
``` javascript
var canvas = document.getElementById('canvas');
    if (canvas.getContext){
    var ctx = canvas.getContext('2d');
    ctx.beginPath();
    ctx.moveTo(75,50);
    ctx.lineTo(100,75);
    ctx.lineTo(100,25);
    ctx.fill();
}
```
### 命令式编程的特点在于，命令“机器”如何去做事情(how)，这样不管你想要的是什么(what)，它都会按照你的命令实现。从代码中可以看到，使用Canvas原生的基于命令式api绘制图形，代码的可读性和可维护性都比较差。而声明式编程的特点在于：告诉“机器”你想要的是什么(what)，让机器想出如何去做(how)。例如如下的代码：
``` javascript
var line = Van.Line({x1,x2,y1,y2});
line.x1 = 10;
line.x2 = 20;
```
### 以上就是基于声明式编程的图形操作，我们并不需要关心组件底层是如何调用canvas api进行图形操作，而仅仅将绘制图形需要的参数（实例代码中为直线起止点的坐标）传入构建组件的参数中，就可以得出想要的结果。同时，当我们需要对图形属性进行修改时，只需要修改参数的值就能更改图形。这样使得代码更为直观，可读性和可维护性都有了一个非常大的提升。

## 组件的创建
### 使用vanjs创建一个组件的代码如下：
``` javascript
var van = new Van({
    // 传入参数
    // ...
});
```
### Vanjs使用对象声明的形式创建组件，在创建组件时需要将组件参数传入，至于图形绘制以及内部业务逻辑都由组件内部进行控制。事实上，在Vanjs的设计中，所有组件实例事实上都是一个Van类的实例，通过使用new Van的方式创建生成。然而，由于普通组件和根组件有着不同的需求和应用场景，所以创建的方式也存在一定的差异，主要的区别在于：
* 根组件只需创建并实例化一次
* 普通组件可能在不同的地方以不同的参数实例化多次
### 由于场景的不同，所以两种组件采取了不同的创建方式
1. 根组件就是所有组件共同的父级组件，在创建时需传入el属性，el属性为页面元素的id，用以指定Canvas画布创建的位置。同时根组件在创建时就直接实例化，无需生成Component对象。
2. 普通组件在创建时一般分为创建和实例化两个步骤，首先使用Van.component创建一个组件对象，然后通过该组件对象创建组件实例，具体操作如下：
``` javascript
// 1.使用Van.component声明一个根组件
var Shine = Van.component({
    // 组件参数
});
// 2.使用Component.prototype.newInstance实例化该组件
var shine = Shine.newInstance();
// 3.其实，我们也可以在实例化的同时传入参数覆盖原有参数
var shine2 = Shine.newInstance({x1:20,y1:40});
```
### 从以上代码中我们可以看到，普通组件的使用包含两个过程
1. 使用Van.component声明一个组件：此过程的返回值是一个Component对象
2. 使用Van.prototype.newInstance获得组件实例，此过程内部调用new Van生成一个Van对象，所以返回值是一个Van对象

### Van、Component实现方式
### Van组件初始化
#### /src/index.js
``` javascript
function Van(options) {
    this._init(options);
}
```
#### /src/internal.js
``` javascript
Van.prototype._init = function(options) {
    // 根据组件参数
}
```
#### Van组件初始化参数options介绍
* el: canvas挂载的节点的id，如果包含该属性的话，则说明该组件为根组件
* data: 传入数据，这些数据可在组件内部通过this.xxx的方式访问
* off: 组件是否为离屏组件
* render: 指定组件的渲染方式，通过调用原生的绘图api以及使用组件的data属性绘制图形
* 生命周期钩子： 主要包括beforeInit、afterInit、beforeRender、afterRender等生命周期钩子，通过使用生命周期钩子可以指定在某个生命周期时的行为。
* components： 组件的子组件
* handlers: 组件的消息接收器
* area: 用于确定是否处在组件图形的范围之类
#### 这些参数在后文中都会有更为详细的介绍

### Component设计
``` javascript
function Component(options) {
    this.options = options;
    return this;
}

Component.prototype.newInstance = function(args) {

    // get param
    var instance;

    // clone options
    var option = mergeTo(this.options, {});

    // merge param and component data
    if (typeof args === 'function') {

    // 直接构造Van实例返回
    instance = new Van(option);
        args.call(instance);
    } else {
        option.data = mergeTo(args, option.data);
        instance = new Van(option);
    }

    instance.$isInstance = true;

    return instance;
};

Component.prototype.newOffInstance = function(args) {
    var instance;

    // ......

    return instance;
};
```
#### 从以上代码中可以看到Component类主要用于存储options,让其成为一个组件模板。当我们调用Component的newInstance方法后，newInstance首先会合并传入参数与Component存储的参数。然后使用合并后的参数，调用Van构造函数返回Van组件实例。
***
### 组件生命周期控制

### Van组件中的生命周期

#### 这里应该放一张图片(生命周期流程图)

### beforeInit
### beforeCreate
### afterCreate
### afterInit