控件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);
此时可以使用 invalidate
或 update
强制更新。
两者的区别在于: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";
}
}
注意
- 不能在
constructor
和render
阶段访问子控件。- 如果匹配到了自定义控件的根节点,则
find
返回控件对象而非节点本身。- 只能使用
find("")
获取根节点,不能通过选择器获取。- 无法通过大写标签名获取自定义控件(如
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");
}
}
注意
- 不要在 constructor 和 render 阶段访问原生节点。
- 尽量不要直接操作原生节点。
直接使用虚拟节点
使用 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 需要转换工具,如 Babel、TypeScript。
性能优化
网页中,最慢的操作是更新 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>;
}
}
控件规范
- 控件命名必须首字母大写。
- 假如控件名为
MyButton
,那么控件内部的 CSS 类必须前缀mybutton-
。 这样可以避免 CSS 类重名导致控件冲突。 - 控件只能访问当前节点,不能访问父控件,更不能访问
document
。 - 每个控件的功能应该相对独立,如果两个控件相互依赖,应考虑合并为一个控件。
- 控件不需要为样式定义额外的 JS 接口,而应该让用户书写 CSS 覆盖默认样式。
实战演示
如何:实现一个计时器
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 的用法相似,但底层实现原理完全不同。
差异
Control
体积更小,去除注释后仅 22k(含所有功能),适用于移动端。- 由于
Control
默认不触发子控件的更新,性能上也更高,特别在控件数多的情况性能提升非常明显。 Control
充分利用了 ES7 的语法,实现同样的需求代码量比 React 少 40%。
Control
从 React 到 Control
相当于React.Component
。Control
中普通属性相当于 React 的props
。Control
中带@bind
的属性相当于 React 的state
。Control
的render
等价于 React 的render
。Control
的init
等价于 React 的componentDidMount
。Control
的update
等价于 React 的forceUpdate
。Control
的uninit
等价于 React 的componentWillUnmount
。
模拟 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:
获取指定对象关联的数据对象。
返回值类型: 返回一个数据对象。 |
获取指定对象关联的数据对象。 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
⮞
bind(...)(target?:
绑定属性到指定子控件或节点的属性。开头表示当前控件的属性。
返回值类型: 绑定属性。如果属性被重新赋值则在下一帧重新渲染整个控件。
返回值类型: 绑定属性到指定子控件或节点的属性。开头表示当前控件的属性。
返回值类型: 绑定属性到指定的子控件或节点本身。开头表示当前控件的属性。
返回值类型: 绑定属性。如果属性被重新赋值则在下一帧重新渲染整个控件。 返回值类型: |
绑定属性到指定子控件或节点的属性。开头表示当前控件的属性。 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
⮞
from(value)(value:
|
获取一个控件或节点。 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
⮞
render(parent, content)(parent:
|
将指定的内容渲染到指定的容器。 |
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 类名。 |
⮞
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()():
(保护的)当被子类重写时负责在关联元素后初始化当前控件。 返回值类型: |
(保护的)当被子类重写时负责在关联元素后初始化当前控件。 |
||||||||
⮞
uninit()():
(保护的)当被子类重写时负责在元素被取消关联前取消初始化当前控件。 返回值类型: |
(保护的)当被子类重写时负责在元素被取消关联前取消初始化当前控件。 |
||||||||
⮞
update()():
重新渲染当前控件。 返回值类型: |
重新渲染当前控件。 |
||||||||
⮞
render()():
|
(保护的)当被子类重写时负责返回当前控件的虚拟节点。 |
||||||||
⮞
layout(changes)(changes:
|
重新布局当前控件。 |
||||||||
⮞
invalidate()():
使当前控件无效并在下一帧重新渲染。 返回值类型: |
使当前控件无效并在下一帧重新渲染。 |
||||||||
⮞
renderTo(parent, ...)(parent:
|
将当前控件渲染到指定的父控件或节点。 |
||||||||
⮞
find(selector)(selector:
在当前控件查找指定的子控件或节点。
返回值类型: 返回子控件或节点。如果找不到则返回 null。 |
在当前控件查找指定的子控件或节点。 |
||||||||
⮞
query(selector)(selector:
|
在当前控件查找匹配的所有子控件或节点。 |
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:
|
初始化新的虚拟节点。 |
||||||||||||||||||||||||
⮞
append(child)(child:
添加一个或多个子节点。
返回值类型: |
添加一个或多个子节点。 |
||||||||||||||||||||||||
⮞
VNode.create(type, props, ...)(type:
|
创建一个虚拟节点。 |
||||||||||||||||||||||||
⮞
VNode.sync(newVNode, ...)(newVNode:
|
生成虚拟节点对应的真实节点或控件。 |
||||||||||||||||||||||||
⮞
VNode.getPropType(type, prop)(type:
|
判断控件类型是否包含指定的属性。 |
||||||||||||||||||||||||
⮞
VNode.getOwnPropTypes(type)(type:
|
获取控件类型本身的属性列表。 |
||||||||||||||||||||||||
⮞
VNode.alwaysSet(type, prop, target)(type:
|
判断是否需要强制更新指定的属性。 |
||||||||||||||||||||||||
⮞
VNode.get(target, prop, ...)(target:
获取节点的属性。
返回值类型: 返回属性值。 |
获取节点的属性。 |
||||||||||||||||||||||||
⮞
VNode.set(target, prop, ...)(target:
设置节点的属性。
返回值类型: |
设置节点的属性。 |
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 类型
表示类节点内容。