渲染小部件
Dojo 是一个响应式框架,它在幕后处理数据更改传播和相关渲染更新的职责。 Dojo 利用虚拟 DOM (VDOM) 概念来表示用于输出的元素,VDOM 中的节点是简单的 JavaScript 对象,旨在比实际 DOM 元素更有效地供开发人员使用。
应用程序只需要关心将它们预期的输出结构声明为虚拟 DOM 节点的层次结构,通常作为它们 小部件的渲染函数 的返回值完成。 框架的 Renderer
组件随后将预期输出与 DOM 中的具体元素同步。 虚拟 DOM 节点还通过传递属性来配置和为小部件和元素提供状态。
Dojo 支持子树渲染,这意味着当状态发生变化时,框架能够确定受更改影响的 VDOM 节点的特定子集。 然后,仅更新 DOM 树中相应的子树以反映更改,从而提高渲染性能并改善用户交互性和体验。
注意: 从小部件渲染函数返回虚拟节点是应用程序围绕渲染的唯一关注点。 尝试使用任何其他实践 在 Dojo 应用程序开发中被认为是一种反模式,应避免。
TSX 支持
Dojo 支持使用称为 TypeScript 中的 tsx
的 jsx
语法扩展。 此语法允许更方便地表示小部件的 VDOM 输出,该输出更接近于构建的应用程序中的结果 HTML。
启用 TSX 的应用程序
启用 TSX 的项目可以通过 dojo create app --tsx
CLI 命令 轻松地进行脚手架。
对于未以这种方式进行脚手架的 Dojo 项目,可以使用以下添加到项目 TypeScript 配置中的内容来启用 TSX
./tsconfig.json
{
"compilerOptions": {
"jsx": "react",
"jsxFactory": "tsx"
},
"include": ["./src/**/*.ts", "./src/**/*.tsx", "./tests/**/*.ts", "./tests/**/*.tsx"]
}
TSX 小部件示例
具有 .tsx
文件扩展名的小部件可以通过从 @dojo/framework/core/vdom
模块导入 tsx
函数,从它们的渲染函数输出 TSX。
src/widgets/MyTsxWidget.tsx
基于函数的变体
import { create, tsx } from '@dojo/framework/core/vdom';
const factory = create();
export default factory(function MyTsxWidget() {
return <div>Hello from a TSX widget!</div>;
});
基于类的变体
import WidgetBase from '@dojo/framework/core/WidgetBase';
import { tsx } from '@dojo/framework/core/vdom';
export default class MyTsxWidget extends WidgetBase {
protected render() {
return <div>Hello from a TSX widget!</div>;
}
}
需要返回多个顶级 TSX 节点的小部件可以将它们包装在 <virtual>
容器元素中。 这比返回节点数组更清晰的选择,因为它允许在 TSX 块中进行更自然的自动代码格式化。 例如
src/widgets/MyTsxWidget.tsx
基于函数的变体
import { create, tsx } from '@dojo/framework/core/vdom';
const factory = create();
export default factory(function MyTsxWidget() {
return (
<virtual>
<div>First top-level widget element</div>
<div>Second top-level widget element</div>
</virtual>
);
});
使用 VDOM
VDOM 节点类型
Dojo 在其 VDOM 中识别两种类型的节点
VNode
,或虚拟节点,它们是具体 DOM 元素的虚拟表示,是所有 Dojo 应用程序的最低级渲染输出。WNode
,或小部件节点,它们将 Dojo 小部件绑定到 VDOM 层次结构中。
VNode
和 WNode
都被认为是 Dojo 虚拟 DOM 中 DNode
的子类型,但应用程序通常不会以抽象意义处理 DNode
。 使用 TSX 语法 也是首选,因为它允许应用程序使用统一的语法渲染两种虚拟节点类型。
实例化 VDOM 节点
如果不需要 TSX 输出,小部件可以导入 @dojo/framework/core/vdom
模块提供的 v()
和 w()
原语之一或两者。 它们分别创建 VNode
和 WNode
,并且可以用作 小部件的渲染函数 的返回值的一部分。 它们的签名,在抽象术语中,是
v(tagName | VNode, properties?, children?)
:w(Widget | constructor, properties, children?)
参数 | 可选 | 描述 |
---|---|---|
`tagName | VNode` | 否 |
`Widget | constructor` | 否 |
properties |
v : 是,w : 否 |
用于配置新创建的 VDOM 节点的 属性集。 这些还允许框架确定节点是否已更新,因此应该重新渲染。 |
children |
是 | 要作为新创建节点的子节点渲染的节点数组。 如果需要,这还可以包括任何文本节点子节点作为文字字符串。 小部件通常封装它们自己的子节点,因此此参数更有可能与 v() 而不是 w() 一起使用。 |
虚拟节点示例
以下示例小部件包含一个更典型的小部件,它返回一个 VNode
。 它具有一个简单的 div
DOM 元素的预期结构表示,该元素包含一个文本子节点
src/widgets/MyWidget.ts
基于函数的变体
import { create, v } from '@dojo/framework/core/vdom';
const factory = create();
export default factory(function MyWidget() {
return v('div', ['Hello, Dojo!']);
});
基于类的变体
import WidgetBase from '@dojo/framework/core/WidgetBase';
import { v } from '@dojo/framework/core/vdom';
export default class MyWidget extends WidgetBase {
protected render() {
return v('div', ['Hello, Dojo!']);
}
}
组合示例
类似地,小部件可以使用 w()
方法相互组合,还可以输出两种类型的多个节点以形成更复杂的结构层次结构
src/widgets/MyComposingWidget.ts
基于函数的变体
import { create, v, w } from '@dojo/framework/core/vdom';
const factory = create();
import MyWidget from './MyWidget';
export default factory(function MyComposingWidget() {
return v('div', ['This widget outputs several virtual nodes in a hierarchy', w(MyWidget, {})]);
});
基于类的变体
import WidgetBase from '@dojo/framework/core/WidgetBase';
import { v, w } from '@dojo/framework/core/vdom';
import MyWidget from './MyWidget';
export default class MyComposingWidget extends WidgetBase {
protected render() {
return v('div', ['This widget outputs several virtual nodes in a hierarchy', w(MyWidget, {})]);
}
}
渲染到 DOM
应用程序为 Dojo 的 renderer()
原语提供一个渲染工厂函数,该原语作为 @dojo/framework/core/vdom
模块的默认导出提供。 提供的工厂定义了应用程序预期 VDOM 结构输出的根。
应用程序通常在它们的主入口点 (main.tsx
/main.ts
) 中调用 renderer()
,然后将返回的 Renderer
对象安装到应用程序 HTML 容器中的特定 DOM 元素中。 如果在安装应用程序时未指定元素,则默认情况下使用 document.body
。
例如
src/main.tsx
import renderer, { tsx } from '@dojo/framework/core/vdom';
import MyComposingWidget from './widgets/MyComposingWidget';
const r = renderer(() => <MyComposingWidget />);
r.mount();
MountOptions
属性
Renderer.mount()
方法接受一个可选的 MountOptions
参数,该参数配置安装操作的执行方式。
属性 | 类型 | 可选 | 描述 |
---|---|---|---|
sync |
boolean |
是 | 默认值:false 。 如果为 true ,则相关渲染生命周期回调(特别是 after 和 deferred 渲染回调)将同步运行。 如果为 false ,则回调将改为在下次重绘之前通过 window.requestAnimationFrame() 异步调度运行。 同步渲染回调在特定节点需要存在于 DOM 中的罕见情况下可能是有益的,但此模式不推荐用于大多数应用程序。 |
domNode |
HTMLElement |
是 | 对 VDOM 应该在其中渲染的特定 DOM 元素的引用。 如果未指定,则默认为 document.body 。 |
registry |
Registry |
是 | 要在已安装的 VDOM 中使用的可选 Registry 实例。 |
例如,要将 Dojo 应用程序安装到除 document.body
之外的特定 DOM 元素中
src/index.html
<!DOCTYPE html>
<html lang="en-us">
<body>
<div>This div is outside the mounted Dojo application.</div>
<div id="my-dojo-app">This div contains the mounted Dojo application.</div>
</body>
</html>
src/main.tsx
import renderer, { tsx } from '@dojo/framework/core/vdom';
import MyComposingWidget from './widgets/MyComposingWidget';
const dojoAppRootElement = document.getElementById('my-dojo-app') || undefined;
const r = renderer(() => <MyComposingWidget />);
r.mount({ domNode: dojoAppRootElement });
卸载应用程序
为了完全卸载 Dojo 应用程序,renderer
提供了一个 unmount
API,它将移除 DOM 节点并对当前渲染的所有小部件执行任何已注册的销毁操作。
const r = renderer(() => <App />);
r.mount();
// To unmount the dojo application
r.unmount();
将外部 DOM 节点添加到 VDOM
Dojo 可以包装外部 DOM 元素,有效地将它们带入应用程序的 VDOM 并将它们用作渲染输出的一部分。这是通过 @dojo/framework/core/vdom
模块中的 dom()
实用程序方法实现的。它的工作原理类似于 v()
,但它以现有的 DOM 节点而不是元素标签字符串作为其主要参数。它返回一个 VNode
,它引用传递给它的 DOM 节点,而不是使用 v()
时新创建的元素。
一旦 dom()
返回的 VNode
被添加到应用程序的 VDOM 中,Dojo 应用程序就会有效地接管包装的 DOM 节点。请注意,此过程仅适用于 Dojo 应用程序外部的节点 - 既可以是包含已安装应用程序的元素的同级元素,也可以是与主网页 DOM 断开连接的新创建的节点。包装作为应用程序安装目标元素的祖先或后代的节点将不起作用。
dom()
API
dom({ node, attrs = {}, props = {}, on = {}, diffType = 'none', onAttach })
参数 | 可选 | 描述 |
---|---|---|
node |
否 | 应该添加到 Dojo VDOM 的外部 DOM 节点 |
attrs |
是 | 应该应用于外部 DOM 节点的 HTML 属性 |
props |
是 | 应该附加到 DOM 节点的属性 |
on |
是 | 要应用于外部 DOM 节点的事件集 |
diffType |
是 | 默认值:none 。当确定外部 DOM 节点是否需要从 Dojo 应用程序内部更新时要使用的 更改检测策略 |
onAttach |
是 | 节点附加到 DOM 后执行的可选回调 |
外部 DOM 节点更改检测
通过 dom()
添加的外部节点与常规虚拟 DOM 节点有一步之遥,因为它们有可能在 Dojo 应用程序之外进行管理。这意味着 Dojo 不能使用 VNode
的属性作为元素的主状态,而是必须依赖于 DOM 节点本身的底层 JavaScript 属性和 HTML 属性。
dom()
接受一个 diffType
属性,允许用户为包装的节点指定属性更改检测策略。考虑到包装节点的预期用途,特定的策略可以帮助 Dojo 确定属性或属性是否已更改,因此需要应用于包装的 DOM 节点。默认策略是 none
,这意味着 Dojo 将在每个渲染周期中简单地将包装的 DOM 元素按原样添加到应用程序的输出中。
注意:所有策略都使用来自先前 VNode
的事件,以确保它们在每次渲染时都正确移除和应用。
可用的 dom()
更改检测策略
diffType |
描述 |
---|---|
none |
此模式将一个空对象作为包装 VNode 中的先前 attributes 和 properties 传递,这意味着传递给 dom() 的 props 和 attrs 将始终在每个渲染周期中重新应用于包装的节点。 |
dom |
此模式使用 DOM 节点的 attributes 和 properties 作为基础来计算传递给 dom() 的 props 和 attrs 是否存在差异,然后需要应用这些差异。 |
vdom |
此模式将使用先前 VNode 进行差异,这实际上是 Dojo 的默认 VDOM 差异策略。对包装节点直接进行的任何更改都将被忽略,不会进行更改检测和渲染更新。 |