Teal TealUI

控件ui/control

控件(Control)即 UI 组件(UI Component)。 比如弹窗、下拉框等提供界面和交互功能的组件都称为控件。

基础
渲染 HTML
自定义控件
传递属性
数据绑定(MVVM)
控件嵌套
控件内容
绑定事件
访问子控件
访问原生节点
直接使用虚拟节点
生命周期
constructor: 构造函数
update: 更新
init: 初始化
renderTo: 渲染
多次 update...
uninit: 反初始化
控件通信
父 → 子
子 → 父
子 → 子
统一数据管理
原理和优化
控件原理
数据绑定原理
JSX
性能优化
控件规范
实战演示
如何:实现一个计时器
如何:实现一个简单的 TODO 列表
针对 React 用户
差异
从 React 到 Control
API
全局
ControlState 枚举
Control 类
VNode 类
Changes 枚举
PropType 枚举
NodeLike 类型

基础

渲染 HTML

使用全局的 render 可以将一段 HTML 渲染到文档上。

import { VNode, render } from "ui/control";

render(
    document.body,
    <h1>Hello world</h1>
);

上述示例中 <h1>Hello world</h1> 是一种和 XML 类似的 JSX 语法,它可以被转换为标准 JavaScript 然后在浏览器运行,详见 JSX 语法

自定义控件

自定义控件是一个继承于 Control 的类,在类内部实现 render 并返回控件的内容。

自定义的控件可以在 render 中直接使用。

import Control, { VNode, render } from "ui/control";

class MyButton extends Control {
    render() {
        return <button class="btn">Hello world</button>;
    }
}

render(
    document.body,
    <MyButton />
);

传递属性

render 中可以通过 {this.xx} 使用控件的自身属性。

import Control, { VNode } from "ui/control";

class MyButton extends Control {
    name = "world";
    render() {
        return <button class={"btn " + this.extraClass}>Hello {this.name}</button>;
    }
}

var btn = render(
    document.body,
    <MyButton extraClass="btn-primary"/>
);

数据绑定(MVVM)

界面是根据数据渲染出来的,通过数据绑定,可以让界面在每次数据改变后自动更新,减少手动更新界面的工作量。

给属性添加 @bind 后,每当该属性被重新赋值,render 都会重新执行并更新界面。

import Control, { VNode } from "ui/control";

class MyButton extends Control {
    @bind name = "world";
    @bind extraClass;
    render() {
        return <button class={"btn " + this.extraClass}>Hello {this.name}</button>;
    }
}

var btn = render(
    document.body,
    <MyButton extraClass="btn-primary"/>
);

setTimeout(() => {
    btn.name = "teal"; // 更新数据,界面将自动更新。
}, 1000);

如果绑定的数据是一个引用对象,仅改变某个属性是不会触发更新的,因为对象本身并未改变。

import Control, { VNode, bind } from "ui/control";

class MyButton extends Control {
    @bind state = { name: "world" };
    render() {
        return <button class="btn">Hello {this.state.name}</button>;
    }
}

var btn = render(
    document.body,
    <MyButton/>
);

setTimeout(() => {
    btn.state.name = "teal"; // 注意:不会触发更新,必须改成:btn.state = {name: "teal"}
}, 1000);

此时可以使用 invalidateupdate 强制更新。 两者的区别在于:update 会立即更新,而 invalidate 会延时更新,同一时间调用多次 invalidate 后最后仅更新一次界面,以此提高性能。

import Control, { VNode, bind } from "ui/control";

class MyButton extends Control {
    @bind state = { name: "world" };
    render() {
        return <button class="btn">Hello {this.state.name}</button>;
    }
}

var btn = render(
    document.body,
    <MyButton/>
);

setTimeout(() => {
    btn.state.name = "teal";
    btn.invalidate(); // 手动触发更新。
}, 1000);

控件嵌套

render 中,如果标签首字母大写,那么它将被解析为自定义控件,而不是 HTML 元素。

import Control, { VNode } from "ui/control";

class App extends Control {
    render() {
        return <div>
                <MyButton name="teal" />
            </div>;
    }
}

class MyButton extends Control {
    name = "world";
    render() {
        return <button class="btn">Hello {this.name}</button>;
    }
}

new App().renderTo(document.body);
// => <div>
//      <button class="btn">Hello teal</button>
//  </div>
不要嵌套自身

如果控件在 render 中嵌套了自身,会引发死循环。

控件内容

默认控件内容都会被添加到控件的 body 属性对应的节点。

控件中可以为 render() 添加参数以便自定义处理控件内容。

import Control, { VNode } from "ui/control";

class App extends Control {
    render() {
        return <div>
                <MyButton disabled={true}>teal</MyButton>
            </div>;
    }
}

class MyButton extends Control {
    render(children, props) {
        return <button class="btn" {...props}>Hello {children}</button>; // 此处 children = [teal]
    }
}

new App().renderTo(document.body);
// => <div>
//      <button class="btn" disabled>Hello teal</button>
//  </div>

children 始终是一个数组,每个项都是一个虚拟节点,如果其是文本节点,可以使用 children[i].props 获取原始数据。

绑定事件

通过节点的 onXXX(其中 XXX 是具体的事件名) 属性可绑定事件函数。 函数接收两个参数,分别是原生事件参数和发生事件的源对象。 为了确保事件执行时 this 始终是控件本身,事件函数应使用箭头函数语法。

import Control, { VNode } from "ui/control";

class MyButton extends Control {
    render() {
        return <button class="btn" onClick={this.handleClick}>Hello world</button>;
    }
    handleClick = (e, s) => {
        // this 是 MyButton
        // s 是触发事件的原始对象,即 <button>
    }
}

访问子控件

使用 控件.find(CSS 选择器) 获取内部的子控件或节点。

使用 控件.find("") 获取根控件或节点。

如果有多个匹配项,find 只返回最先出现的项。 如果需要获取所有匹配项,使用 控件.query

import Control, { VNode } from "ui/control";

class MyButton extends Control {
    render() {
        return <div>
                <button class="btn" onClick={this.handleClick}>Hello world</button>
            </div>;
    }
    handleClick = e => {
        this.find(".btn").innerHTML = "Hello Teal";
    }
}
注意
  1. 不能在 constructorrender 阶段访问子控件。
  2. 如果匹配到了自定义控件的根节点,则 find 返回控件对象而非节点本身。
  3. 只能使用 find("") 获取根节点,不能通过选择器获取。
  4. 无法通过大写标签名获取自定义控件(如 find("MyButton") 是无效的)。

访问原生节点

控件本质上就是一段 HTML。 控件封装了一些 API 用于操作其内部关联的 HTML。

使用 控件.elem 获取控件关联的原生节点。

可以使用DOM 处理进行后续处理:

import Control from "ui/control";
import * as dom from "dom";

class MyButton extends Control {
    render() {
        return <button class="btn" onClick={this.handleClick}>Hello world</button>;
    }
    handleClick = e => {
        dom.addClass(this.elem, "btn-disabled");
    }
}

也可以使用 jQuery 等第三方框架:

import Control from "ui/control";
import $ from "jquery";

class MyButton extends Control {
    render() {
        return <button class="btn" onClick={this.handleClick}>Hello world</button>;
    }
    handleClick = e => {
        $(this.elem).addClass("btn-disabled");
    }
}
注意
  1. 不要在 constructor 和 render 阶段访问原生节点。
  2. 尽量不要直接操作原生节点。

直接使用虚拟节点

使用 from 可以直接将虚拟节点转为真实节点。

import { VNode, from } from "ui/control";

var jsx = <button class="btn">Hello world</button>;

var btn = from(jsx);

document.body.appendChild(btn);

生命周期

控件从创建到销毁会依次执行下列函数:

constructor → update → init → renderTo → 多次 update... → uninit

constructor: 构造函数

控件的构造函数是在 new 控件名() 时执行的函数。 构造函数内无法获取当前关联的 elem,主要用于设置当前控件的默认属性。

update: 更新

update 主要用于生成或者重新生成当前控件关联的 HTML 节点。 默认地,update 会先调用 render 生成最新的虚拟节点,然后通过 VNode.sync 将其转为真实的节点, 转换时会比较上一次生成的虚拟节点并尽可能地重用上一次生成的真实节点,确保性能最优。

render 中无法使用 elem,也不能更新任何数据。 render 可以返回 null 表示当前控件为空。

init: 初始化

当为控件关联一个新的 elem 后,都会执行 init

init 中可以使用 elem,也可以更新数据。 一般地,init 主要用于绑定附加事件。

renderTo: 渲染

将当前控件添加到文档树中。添加后控件才会显示。

多次 update...

每次更改通过 @bind 绑定的属性后,都会执行 invalidate,并在延时后调用 update 更新界面。

uninit: 反初始化

当之前关联的元素不再被关联前,会执行 uninit,在 uninit 中可以使用 elem。 一般地,uninit 主要用于解绑附加事件。

控件通信

父 → 子

在父控件的 render 中可以直接将数据传递给子控件。

class App extends Control {
    data = 1;
    render() {
        return <div>
                <MyButton name={this.data}></MyButton>
            </div>;
    }
}

子 → 父

子控件可通过事件将数据传递给父控件。

class App extends Control {
    render() {
        return <div>
                <TextBox onChange={(e, t) => this.handleChange(t.value)}></TextBox>
            </div>;
    }
    handleChange = data => {
        // data 是 TextBox 传递来的数据
    }
}

子 → 子

将两个子控件放在公共父控件中,第一个子控件将数据传递给父控件,然后父控件再传递给第二个子控件。 以此实现子控件之间的互相通信。

统一数据管理

使用全局对象统一管理数据,可避免相互传递数据的麻烦。

var store = {};

class App extends Control {
    render() {
        return <div>
                <MyButton></MyButton>
            </div>;
    }
}

class MyButton extends Control {
    alwaysUpdate = true;
    render() {
        return <button class="btn">Hello {store.content}</button>;
    }
}
注意

默认如果子控件的属性和内容都未发生改变,子控件是不会重新渲染的。 通过设置 alwaysUpdate = true 使子组件强制更新。

在项目中应将控件分为交互效果和业务逻辑两大类。 只有业务逻辑控件才能使用和修改全局数据。

原理和优化

本节面向希望开发高性能控件的用户。

控件原理

任何一个控件都会关联一个 HTML 节点。这个节点可以由 update 生成,也可以直接被用户指定。

class Control {

    // 获取关联的节点
    get elem() {
        if (!this._elem) this.update(); // 如果未指定则生成新的
        return this._elem;
    }

    // 生成默认节点
    update() { 
        // ... 
    }

    // 允许用户手动设置关联的节点
    set elem(value) {
        if (this._elem) this.uninit();
        this._elem = value;
        this.init();
    }

    // 关联的元素改变后执行
    init() {
        // 在这里可以绑定事件
    }

    // 关联的元素改变前执行
    uninit() {
        // 在这里可以解绑事件
    }

    // 添加到文档树
    renderTo(parent) {  
        parent.appendChild(this.elem);
    }

}

假如已经写好了 HTML,可以直接关联:

<button class="btn" id="btn">Hello world!</button>
<script>
    var btn = new MyButton();
    btn.elem = document.getElementById("btn");
</script>

默认地,则调用 update 生成新的节点。 用户可以通过修改 update 自定义生成的节点内容。

import Control from "ui/control";

class MyButton extends Control {

    update() {
        this.elem = document.createElement("button");
        this.elem.className = "btn";
        this.elem.innerHTML = "Hello world!";
    }

}

数据绑定原理

为实现将数据被更改后界面自动更新的效果,需要分三步:

虚拟节点以树结构存储了 HTML 节点的信息,如:

{
    "type": "button",
    "props": {
        "class": "btn"
    },
    "children": [
        {
            "type": null,
            "props": "Hello world"
        }
    ]
}

比较新旧虚拟节点得到一份差异表,然后根据差异表更新真正的节点,以此可以跳过处理未被更新的 DOM 节点,提升性能。

class Control {

    update() {
        var oldVNode = this.vNode || { r: this, props: {}, children: [] };
        var newVNode = this.vNode = this.render();
        this.elem = sync(oldVNode, newVNode);
    }

    // 生成当前控件的虚拟节点。
    render() {
        return null; // Control 默认返回空。子控件则返回对应的虚拟节点。
    }

}

// 将新虚拟节点的改动同步到真正的节点。
function sync(oldVNode, newVNode) {

    // 如果节点类型发生变化则创建新节点;否则重用之。
    var typeChanged = oldVNode.type !== newVNode.type;
    var r = typeChanged ? document.createElement(newVNode.type) : oldVNode.r;

    // 如果创建了新节点则将原节点替换掉。
    if (typeChanged) {
        oldVNode.r.parentNode.replaceChild(r, oldVNode.r);
    }
    
    // 如果属性发生变化则更新属性。
    for (var prop in newVNode.props) {
        if (oldVNode.props[prop] !== newVNode.props[prop]) {
            r[prop] = newVNode.props[prop];
        }
    }

    // 递归更新子节点。
    for(var i = 0; i < newVNode.children.length; i++) {
        sync(oldVNode.children[i] || { r: oldVNode, props: {}, children: [] }, newVNode.children[i]);
    }
        
    return r;
}

最后,通过 Object.defineProperty 使得属性被重新赋值后调用 update

function bind(prototype, prop) {
    Object.defineProperty(prototype, prop, {
        get() {
            return this["_" + prop];
        }
        set(value) {
            this["_" + prop] = value;
            this.update();
        }
    });
}

为了避免每次更新属性都会调用 update,将原有的 update 替换为带延时效果的 invalidate

class Control {

    invalidate() {
        if (this._updateTimer) { // 如果在 1 秒内已经执行过则不再执行。
            return;
        }
        this._updateTimer = setTimeout(() => {
            this.update();
        }, 1);
    }

}

完整的 Control 实现请在页面右上角的工具菜单点击查看源码。

JSX

JSX 语法最终将被转换为虚拟节点(VNode)。

以下代码:

import Control, { VNode, bind } from "ui/control";

class MyButton extends Control {
    @bind name = "world";
    render() {
        return <button class="btn">Hello {this.name}</button>;
    }
}

最终会被编译为:

var control = require("ui/control"),
    Control = control.default, 
    VNode = control.VNode, 
    bind = control.bind;

function MyButton () { 
    this.name = "world";
}

MyButton.prototype = new Control();
MyButton.prototype.constructor = MyButton;

bind(MyButton.prototype, "name");

MyButton.prototype.render = function () {
    return VNode.create("button", { "class": "btn" }, "Hello ", this.name);
};

语法细节请参考 JSX 规范

在项目中使用 JSX 需要转换工具,如 BabelTypeScript

性能优化

网页中,最慢的操作是更新 DOM,其次是大量的运算。 通过虚拟节点对比的方法,真实 DOM 的操作已大幅减少, 因此剩下的性能问题就是计算量较大的 render 执行次数太多导致的。

那么,如何减少 render 执行的次数?

优化一:减少控件数

render 的次数和网页中控件的数量成正比。 减少控件总数对性能改进会有明显的效果。

优化二:避免设置 alwaysUpdate = true

未设置此属性时,仅当组件任一属性发生改变后才会重新渲染组件。

优化三:直接操作节点

直接操作原生节点可以跳过比较虚拟节点的环节。

import Control, { VNode, bind } from "ui/control";

class MyButton extends Control {
    render() {
        return <div>
            <button class="btn">Hello </button>
        </div>;
    }
    set name(value) {
        this.find("button").innerHTML += value;
    }
}

通过 @bind(CSS 选择器) 可以绑定属性到某个子控件或节点。 使用 @bind("") 可以绑定根控件或节点。

import Control, { VNode, bind } from "ui/control";

class MyButton extends Control {
    @bind(".btn") btn; // 绑定到 <button> 元素。
    render() {
        return <div>
            <button class="btn" onClick={this.handleClick}>Hello world!</button>
        </div>;
    }
    handleClick = () => {
        alert(this.btn.innerHTML);
    }
}

使用 @bind(CSS 选择器, 属性) 可以绑定到某个子控件或节点的属性。

import Control, { VNode, bind } from "ui/control";

class MyButton extends Control {
    @bind(".btn", "innerHTML") btnContent; // 绑定到 <button> 元素的 innerHTML 属性。
    render() {
        return <div>
            <button class="btn" onClick={this.handleClick}>Hello world!</button>
        </div>;
    }
    handleClick = () => {
        alert(this.btnContent);
    }
}

如果属性名是 style 可以通过第三参数绑定到某个样式。

import Control, { VNode, bind } from "ui/control";

class MyButton extends Control {
    @bind(".btn", "style", "display") btnDisplay; // 绑定为 <button> 元素的 style.display。
    render() {
        return <div>
            <button class="btn" onClick={this.handleClick}>Hello world!</button>
        </div>;
    }
    handleClick = () => {
        this.btnDisplay = "none";
    }
}

如果属性名是 class 可以通过第三参数绑定到某个类名。

import Control, { VNode, bind } from "ui/control";

class MyButton extends Control {
    @bind(".btn", "class", "btn-primary") btnPrimary; // 绑定为 <button> 元素的 class 是否包含 btn-primary。
    render() {
        return <div>
            <button class="btn" onClick={this.handleClick}>Hello world!</button>
        </div>;
    }
    handleClick = () => {
        this.btnPrimary = true; // 相当于为 <button> 添加 btn-primary 类名。
    }
}

如果属性名是事件可以通过第三参数设置委托。

import Control, { VNode, bind } from "ui/control";

class MyButton extends Control {
    @bind(".btn", "onClick", "span") onButtonClick;
    render() {
        return <div>
            <button class="btn">Hello <span>world!</span></button>
        </div>;
    }
}

控件规范

实战演示

如何:实现一个计时器

import Control, { VNode, bind, from } from "ui/control";

class Timer extends Control {
    @bind time = 0;
    render(){
        return <span>{this.time}</span>;
    }
    constructor() {
        super();
        setInterval(() => { this.time++; }, 1000);
    }
}

export default from(<Timer />).renderTo(__root__);

如何:实现一个简单的 TODO 列表

import Control, { VNode, bind, from } from "ui/control";

class TodoList extends Control {
    @bind todos = [];
    render() {
        return <ul>{this.todos.map((todo, index) => <li>
                <span>{todo}</span>
                <button onClick={() => this.remove(index)}>删除</button>
            </li>)}
                <li>
                    <input type="text" id="newTodo" />
                    <button onClick={() => this.add(this.find("#newTodo").value)}>新增</button>
                </li>
            </ul>;
    }
    remove(index) {
        this.todos.splice(index, 1);
        this.invalidate();
    }
    add(item) {
        this.todos.push(item);
        this.invalidate();
    }
}

export default from(<TodoList />).renderTo(__root__);

针对 React 用户

Control 和 React 的用法相似,但底层实现原理完全不同。

差异

从 React 到 Control

模拟 React 组件的代码如下:

import Control from "ui/control";

class Component extends Control {
    getInitialState() { return {}; }
    componentWillMount() { }
    componentDidMount() { }
    componentWillReceiveProps(nextProps) { } // 此函数无法模拟
    shouldComponentUpdate() { return true; }
    componentWillUpdate() { }
    componentDidUpdate() { }
    componentWillUnmount() { }
    forceUpdate(){ this.update(); }

    constructor (props) {
        super();
        this.props = props || {};
        this.state = this.getInitialState();
        this.componentWillMount();
    }
    init() {
        this.componentDidMount();
    }
    uninit() {
        this.componentWillUnmount();
    }
    update() {
        if (this.shouldComponentUpdate(this.props)) {
            this.componentWillUpdate();
            super.update();
            this.componentDidUpdate();
        }
    }
    setState(name, value) {
        this.state[name] = value;
        this.invalidate();
    }
}

API

全局

函数 描述
data(obj)(obj:any):any

获取指定对象关联的数据对象。

参数 类型 描述 默认值
obj* any

返回值

类型:any

返回一个数据对象。

获取指定对象关联的数据对象。

bind(...)(target?:any, propertyName?:string, selector?:any, prop?:string, args?:any, descriptor?:PropertyDescriptor):PropertyDescriptor | function(5 重载)

绑定属性到指定子控件或节点的属性。开头表示当前控件的属性。

参数 类型 描述 默认值
target* Control
propertyName* string
selector* string
prop* string
args any
descriptor PropertyDescriptor

返回值

类型:void


绑定属性。如果属性被重新赋值则在下一帧重新渲染整个控件。

参数 类型 描述 默认值
target* Control
propertyName* string
descriptor PropertyDescriptor

返回值

类型:void


绑定属性到指定子控件或节点的属性。开头表示当前控件的属性。

参数 类型 描述 默认值
selector* string
prop* string
args any

返回值

类型:PropertyDecorator


绑定属性到指定的子控件或节点本身。开头表示当前控件的属性。

参数 类型 描述 默认值
selector* string

返回值

类型:PropertyDecorator


绑定属性。如果属性被重新赋值则在下一帧重新渲染整个控件。

返回值

类型:PropertyDecorator

绑定属性到指定子控件或节点的属性。开头表示当前控件的属性。

from(value)(value:NodeLike):Control | HTMLElement | Text | DocumentFragment

获取一个控件或节点。

参数 类型 描述 默认值
value* NodeLike

返回值

类型:Control | HTMLElement | Text | DocumentFragment

返回一个控件或节点。如果找不到则返回 null。

获取一个控件或节点。

render(parent, content)(parent:Control | HTMLElement, content:NodeLike):Control | Node

将指定的内容渲染到指定的容器。

参数 类型 描述 默认值
parent* Control | HTMLElement
content* NodeLike

返回值

类型:Control | Node

返回生成的节点。

将指定的内容渲染到指定的容器。

ControlState 枚举

表示控件的状态。

枚举字段 枚举值 描述
initial = 1

初始状态。

1

初始状态。

invalidated = 2

需要重新渲染。

2

需要重新渲染。

rendering = 3

正在渲染。

3

正在渲染。

rendered = 4

已渲染。

4

已渲染。

Control 类

表示一个控件。

字段 类型 描述
readyState : ControlState

获取当前控件的渲染状态。

ControlState

获取当前控件的渲染状态。

elem : HTMLElement

关联的元素。

HTMLElement

关联的元素。

vNode : VNode

(保护的)获取当前控件关联的虚拟节点。

VNode

(保护的)获取当前控件关联的虚拟节点。

sourceVNode : VNode

获取创建该控件使用的源虚拟节点。

VNode

获取创建该控件使用的源虚拟节点。

alwaysUpdate : boolean

控件是否使用主动更新模式。

boolean

控件是否使用主动更新模式。

body : HTMLElement

(只读)获取用于包含子控件和节点的根元素。

HTMLElement

(只读)获取用于包含子控件和节点的根元素。

duration : number = 200

渐变的持续毫秒数。如果为 0 则不使用渐变。

number

渐变的持续毫秒数。如果为 0 则不使用渐变。

class : string

CSS 类名。

string

CSS 类名。

hidden : boolean

是否隐藏。

boolean

是否隐藏。

style : string | { [key: string]: string | number; }

控件样式。

string | object

控件样式。

id : string

控件序号。

string

控件序号。

content : NodeLike

控件内容。

NodeLike

控件内容。

onSelectStart : (e: Event, sender: Control) => void function

选择开始事件。

onClick : (e: MouseEvent, sender: Control) => void function

点击事件。

onAuxClick : (e: MouseEvent, sender: Control) => void function

中键点击事件。

onDblClick : (e: MouseEvent, sender: Control) => void function

双击事件。

onContextMenu : (e: PointerEvent, sender: Control) => void function

右键菜单事件。

onMouseDown : (e: MouseEvent, sender: Control) => void function

鼠标按下事件。

onMouseUp : (e: MouseEvent, sender: Control) => void function

鼠标按上事件。

onMouseOver : (e: MouseEvent, sender: Control) => void function

鼠标移入事件。

onMouseOut : (e: MouseEvent, sender: Control) => void function

鼠标移开事件。

onMouseEnter : (e: MouseEvent, sender: Control) => void function

鼠标进入事件。

onMouseLeave : (e: MouseEvent, sender: Control) => void function

鼠标离开事件。

onMouseMove : (e: MouseEvent, sender: Control) => void function

鼠标移动事件。

onWheel : (e: WheelEvent, sender: Control) => void function

鼠标滚轮事件。

onScroll : (e: UIEvent, sender: Control) => void function

滚动事件。

onTouchStart : (e: TouchEvent, sender: Control) => void function

触摸开始事件。

onTouchMove : (e: TouchEvent, sender: Control) => void function

触摸移动事件。

onTouchEnd : (e: TouchEvent, sender: Control) => void function

触摸结束事件。

onTouchCancel : (e: TouchEvent, sender: Control) => void function

触摸撤销事件。

onPointerEnter : (e: PointerEvent, sender: Control) => void function

指针进入事件。

onPointerLeave : (e: PointerEvent, sender: Control) => void function

指针离开事件。

onPointerOver : (e: PointerEvent, sender: Control) => void function

指针移入事件。

onPointerOut : (e: PointerEvent, sender: Control) => void function

指针移开事件。

onPointerDown : (e: PointerEvent, sender: Control) => void function

指针按下事件。

onPointerMove : (e: PointerEvent, sender: Control) => void function

指针移动事件。

onPointerUp : (e: PointerEvent, sender: Control) => void function

指针松开事件。

onPointerCancel : (e: PointerEvent, sender: Control) => void function

指针取消事件。

onGotPointerCapture : (e: PointerEvent, sender: Control) => void function

指针开始捕获事件。

onLostPointerCapture : (e: PointerEvent, sender: Control) => void function

指针停止捕获事件。

Control.propTypes : { [prop: string]: PropType; }

当前控件的属性列表。

object

当前控件的属性列表。

方法 描述
init()():void

(保护的)当被子类重写时负责在关联元素后初始化当前控件。

返回值

类型:void

(保护的)当被子类重写时负责在关联元素后初始化当前控件。

uninit()():void

(保护的)当被子类重写时负责在元素被取消关联前取消初始化当前控件。

返回值

类型:void

(保护的)当被子类重写时负责在元素被取消关联前取消初始化当前控件。

update()():void

重新渲染当前控件。

返回值

类型:void

重新渲染当前控件。

render()():VNode

(保护的)当被子类重写时负责返回当前控件的虚拟节点。

参数 类型 描述 默认值
children VNode[]
props { [key: string]: any; }

返回值

类型:VNode

返回表示当前控件内容的虚拟节点。如果当前控件不渲染任何内容则返回 null。

(保护的)当被子类重写时负责返回当前控件的虚拟节点。

layout(changes)(changes:Changes):void

重新布局当前控件。

参数 类型 描述 默认值
changes* Changes

返回值

类型:void

重新布局当前控件。

invalidate()():void

使当前控件无效并在下一帧重新渲染。

返回值

类型:void

使当前控件无效并在下一帧重新渲染。

renderTo(parent, ...)(parent:Control | Node, refChild?:Control | Node):void

将当前控件渲染到指定的父控件或节点。

参数 类型 描述 默认值
parent* Control | Node
refChild Control | Node

返回值

类型:void

将当前控件渲染到指定的父控件或节点。

find(selector)(selector:string):Control | HTMLElement

在当前控件查找指定的子控件或节点。

参数 类型 描述 默认值
selector* string

返回值

类型:Control | HTMLElement

返回子控件或节点。如果找不到则返回 null。

在当前控件查找指定的子控件或节点。

query(selector)(selector:string):(Control | HTMLElement)[]

在当前控件查找匹配的所有子控件或节点。

参数 类型 描述 默认值
selector* string

返回值

类型:(Control | HTMLElement)[]

返回子控件或节点列表。

在当前控件查找匹配的所有子控件或节点。

VNode 类

表示一个虚拟节点。

字段 类型 描述
type : string | (new () => Control)

节点类型。如果是字符串表示 HTML 原生节点;如果是 null 表示文本节点;如果是函数表示控件。

string | function

节点类型。如果是字符串表示 HTML 原生节点;如果是 null 表示文本节点;如果是函数表示控件。

props : any

节点属性。如果是文本节点则表示节点内容。

any

节点属性。如果是文本节点则表示节点内容。

children : VNode[]

所有子节点。

VNode[]

所有子节点。

result : Control | HTMLElement | Text

生成的真实节点或控件。

Control | HTMLElement | Text

生成的真实节点或控件。

VNode.props : { [prop: string]: { get(elem: HTMLElement, args?: any): any; set(elem: HTMLElement, value: any, args?: any): void; }; } = { __proto__: null!, class: { get(elem: HTMLElement, args?: string) { if (args) { return dom.hasClass(elem, args); } return elem.className; }, set(elem: HTMLElement, value: string | boolean, args?: string) { if (args) { dom.toggleClass(elem, args, value as boolean); } else { elem.className = value as string; } } }, style: { get(elem: HTMLElement, args?: string) { if (args) { return dom.getStyle(elem, args); } return elem.style; }, set(elem: HTMLElement, value: { [key: string]: string | number } | string | number, args?: string) { if (args) { dom.setStyle(elem, args, value as string | number); } else if (value == null || typeof value === "string") { elem.style.cssText = value as string; } else { for (const key in value as { [key: string]: string | number }) { dom.setStyle(elem, key, (value as { [key: string]: string | number })[key]); } } } }, hidden: { get(elem: HTMLElement) { return dom.getAttr(elem, "hidden") || dom.isHidden(elem); }, set(elem: HTMLElement, value: boolean) { dom.setAttr(elem, "hidden", value); } }, innerHTML: { get: dom.getHtml, set(elem: HTMLElement, value: NodeLike | NodeLike[]) { if (typeof value === "object") { elem.innerHTML = ""; if (Array.isArray(value)) { for (const item of value) { render(elem, item); } } else { render(elem, value); } } else { dom.setHtml(elem, value); } } } }

设置节点特殊属性的读写方式。

object

设置节点特殊属性的读写方式。

方法 描述
new VNode(type, props, ...)(type:string | function, props:any, children?:VNode[]):VNode

初始化新的虚拟节点。

参数 类型 描述 默认值
type* string | (new () => Control)
props* any
children VNode[]

返回值

类型:VNode

初始化新的虚拟节点。

append(child)(child:any):void

添加一个或多个子节点。

参数 类型 描述 默认值
child* any

返回值

类型:void

添加一个或多个子节点。

VNode.create(type, props, ...)(type:string | function, props:any, ...children:any[]):VNode

创建一个虚拟节点。

参数 类型 描述 默认值
type* string | (new () => Control)
props* any
children any[]

返回值

类型:VNode

返回创建的虚拟节点。

创建一个虚拟节点。

VNode.sync(newVNode, ...)(newVNode:VNode, oldVNode?:VNode):Changes

生成虚拟节点对应的真实节点或控件。

参数 类型 描述 默认值
newVNode* VNode
oldVNode VNode

返回值

类型:Changes

返回所有更新标记。

生成虚拟节点对应的真实节点或控件。

VNode.getPropType(type, prop)(type:typeof Control, prop:string):PropType

判断控件类型是否包含指定的属性。

参数 类型 描述 默认值
type* typeof Control
prop* string

返回值

类型:PropType

如果属性无 get/set 操作则返回 true,否则返回 false。

判断控件类型是否包含指定的属性。

VNode.getOwnPropTypes(type)(type:typeof Control):object

获取控件类型本身的属性列表。

参数 类型 描述 默认值
type* typeof Control

返回值

类型:{ [prop: string]: PropType; }

返回属性列表。

获取控件类型本身的属性列表。

VNode.alwaysSet(type, prop, target)(type:string | function, prop:string, target:Control | HTMLElement | Text):boolean

判断是否需要强制更新指定的属性。

参数 类型 描述 默认值
type* string | (new () => Control)
prop* string
target* Control | HTMLElement | Text

返回值

类型:boolean

如果需要强制更新则返回 true,否则返回 false。

判断是否需要强制更新指定的属性。

VNode.get(target, prop, ...)(target:HTMLElement, prop:string, args?:any, root?:any):any

获取节点的属性。

参数 类型 描述 默认值
target* HTMLElement
prop* string
args any
root any

返回值

类型:any

返回属性值。

获取节点的属性。

VNode.set(target, prop, ...)(target:HTMLElement, prop:string, value?:any, args?:any, root?:any):void

设置节点的属性。

参数 类型 描述 默认值
target* HTMLElement
prop* string
value any null
args any
root any

返回值

类型:void

设置节点的属性。

Changes 枚举

表示更新的类型。

枚举字段 枚举值 描述
none = 0

没有任何改变。

0

没有任何改变。

type = 1 << 0

类型发生改变。根节点已重新创建。

1 << 0

类型发生改变。根节点已重新创建。

state = 1 << 1

状态发生改变。控件已重新渲染。

1 << 1

状态发生改变。控件已重新渲染。

prop = 1 << 2

属性发生改变。

1 << 2

属性发生改变。

children = 1 << 3

子控件或元素发生改变。

1 << 3

子控件或元素发生改变。

PropType 枚举

表示控件属性的类型。

枚举字段 枚举值 描述
value = 0

该属性是一个普通的值。

0

该属性是一个普通的值。

state = 1

该属性是一个状态值。设置该属性后需要更新控件。

1

该属性是一个状态值。设置该属性后需要更新控件。

getter = 2

该属性是一个只读访问器。

2

该属性是一个只读访问器。

setter = 3

该属性是一个可写访问器。

3

该属性是一个可写访问器。

NodeLike 类型

表示类节点内容。

同:string | Control | Node | VNode