---
order: 7
navTitle: 组件渲染
---
# 组件渲染

当组件初始化、修改属性、改变大小、修改过滤参数等操作时，都会触发组件渲染。

下图描述了用户、页面和组件之间的核心交互逻辑。

![组件架构图](../../images/viz-infrastructure.png)

## 用户、页面和组件的交互行为

- 设计器选中组件显示属性面板

用户选中组件时设计器会显示组件的属性面板。系统根据[capabilities.json](./capabilities-json.md)定义的属性和属性面板配置，显示当前选中组件的属性。

- 设计器通过属性面板修改组件属性

用户通过属性面板修改组件属性后，系统会触发组件的`render`函数渲染组件。组件渲染时通过`componentBuilder`获取组件的属性。

- 设计器预览

当`仪表板`和`SuperPage`这种所见即所得的模块在设计器中切换预览时，不会重新渲染组件，由于查看区域的视窗大小变化，会触发组件的`render`重新调整组件大小。

- 改变页面大小

用户改变了页面的视窗大小后，页面会从画布开始逐层向下分发大小变化事件，通知每一个组件`render`。

- 修改过滤参数

当改变了模型过滤条件引用的参数自动过滤的组件值后，使用模型查询数据的组件，需要重新发起查询。页面会通知这些组件数据变化，如果组件是`显示`的，那么先发起查询，查询完成后通知组件`render`。

## IVisualComponent

所有的扩展组件开发都需要实现`IVisualComponent`接口。实现类的名字和`package.json`中配置的`visualClassName`一致。

页面在初始化`IVisualComponent`时，会将其包装为一个`BOComponentWrapper`，`BOComponentWrapper`提供渲染组件位置、大小、标题、背景等通用能力，并创建一个包含`vc-body`class的`div`作为`domParent`传递给`IVisualComponent`的`render`函数作为`IVisualComponent`渲染的根DOM。

```typescript
export class MyVisualComponent implements IVisualComponent {

    constructor(args: VisualComponentArgs) {
        //初始化创建组件时调用一次，初始化组件的基本信息
    }

    public render(args: VisualComponentRenderArgs): void | Promise<void> {
        //渲染组件，创建DOM结构，输出样式，绑定事件...
    }
}
```

### constructor

组件初始化时调用`constructor`。在构造函数中可以完成一些组件的初始化操作。

#### VisualComponentArgs

- `visualObject`，可视化页面对象，包含了一些通用服务
- `compBuilder`，组件的属性和对象访问接口，可以获取到组件元数据，下级组件等

### render

组件初始化、数据变化、主题变化等操作时都会调用`render`函数。

#### VisualComponentRenderArgs

- `domParent`，组件的父DOM，组件的DOM结构应该都创建在该DOM内。
- `renderType`，组件渲染类型，渲染时判断类型决定如何增量渲染。
- `viewPort`，组件渲染的大小和位置信息。
- `dataViews`，组件的数据对象，通过`package.json`的`dataViewMappings`配置的查询产生的查询结果。
- `viewMode`，页面的查看模式，包括编辑、预览、查看。
- `modifiedInfo`，组件的增量变化信息。组件在渲染时可以使用该信息进行增量渲染，不用每次都重新渲染整个组件。例如改变了组件的背景，传入的组件。

#### updateType

|名称|值|描述|
|---|---|---|
|None|0|无变化|
|Data|1|数据变化，`dataViews`和`modifiedInfo`中包含了变化的信息|
|Theme|2|主题变化|
|ViewMode|4|切换预览|
|Resize|8|尺寸变化|
|Visible|16|显示隐藏变化|
|All|128|全量刷新|

#### modifiedInfo

### dispose

销毁组件，组件被删除时调用。默认组件会将`IVisualComponent`移除出`domParent`，如有额外的销毁逻辑，可实现该函数。

## 增量渲染组件大小变化

当组件所在父容器尺寸变化时（比如用户拖动了浏览器窗口的大小），会触发组件渲染，`renderType`为`ComponentRenderType.Resize`。

如果组件本身就是自适应的，组件的尺寸变化时内部的DOM元素会自动重新布局，那么可以不处理该增量变化。

如果组件内部的布局需要由程序进行控制，比如用canvas绘制的图形，当组件尺寸变化时，canvas需要重新绘制，那么需要实现此函数。实现者可以简单的重新调用一下`render`函数，实现全量渲染，也可以按需自己实现更精细化的增量渲染机制。

```ts
export class MyVisualComponent implements IVisualComponent {

    public render(args: VisualComponentRenderArgs): void {
        let renderType = args.renderType;
        if (renderType & ComponentRenderType.Resize) {
            this.doResize();
        }
    }

    private doResize(): void {
        this.charts.resize();
    }
}
```

## 弹出组件

弹出组件在没有弹出时不输出DOM，通过调用组件方法显示和隐藏组件。

弹出组件在隐藏时应该不响应任何事件，当显示时才一次性更新组件为最新状态。

在设计器中，弹出组件显示成一个带名称的方块，双击编辑后显示一个占据整个画布的编辑区。

TODO 截图

以菜单组件为例。通过调用组件方法交互，设置`show`显示菜单，`hide`隐藏菜单。

```json
{
    "name": "MyMenu",
    "popupComponent": true,
    "accessableMethods": [{
        "name": "show"
    }, {
        "name": "hide"
    }]
}
```

```ts
export class MyMenu implements IVisualComponent {

    private visible: boolean;

    private menu: Menu;

    public render(args: VisualComponentRenderArgs): void {
        if(!visible || !this.menu) {
            return;
        }
        //render menu when visible
    }

    public show(args: BOInvokeComponentMethodArgs): void {
        this.visible = true;
        let menu = this.menu;
        if (!menu) {
            menu = this.menu = new Menu();
        }
        //init items from data
        let items;
        menu.setItems(items);
        menu.showAt(event);
    }

    public hide(args: BOInvokeComponentMethodArgs): void {
        this.visible = false;
        this.menu?.hide();
    }
}
```

## 逻辑组件

逻辑组件不输出任何DOM元素，在`render`中执行程序逻辑，通常用于定时器等场景。

逻辑组件在初始化或数据变化时才调用`render`函数，不响应其它类型事件。

以定时器组件为例，页面全部加载完毕后开始计时，可以通过组件方法`start`和`stop`开始和停止计时。

```json
{
    "name": "MyTimer",
    "logicComponent": true,
    "accessableMethods": [{
        "name": "start"
    }, {
        "name": "stop"
    }]
}
```

```ts
export class MyTimer implements IVisualComponent {

    private stopped: boolean;

    public render(args: VisualComponentRenderArgs): void {
        this.start();
    }

    public start(args: BOInvokeComponentMethodArgs): void {
        this.stopped = false;
        // start timer
    }

    public stop(args: BOInvokeComponentMethodArgs): void {
        this.stopped = true;
    }
}
```
