dojo dragon main logo

通过属性配置部件

传递给 VDOM 中节点的属性(properties)概念是 Dojo 的核心支柱。节点属性充当在应用程序中传播状态的主要管道,可将其从父部件传给子部件,也可以通过事件处理器逐层回传。它们也可以作为使用者与部件交互的重要 API,为父部件传入属性来配置其 DOM 结构(返回 VNode),也可以传给其管理的子部件(返回 WNode)。

VNode 接收 VNodeProperties 类型的属性,WNode 最低接收 WidgetProperties。部件的作者通常会定义自己的属性接口,然后需要调用者传入该接口。

VDOM 节点的 key

Widgetproperties 非常简单,只包含一个可选属性 key,该属性也存在于 VNodeProperties 中。

当部件开始输出的多个元素,处在 VDOM 的同一个层级,并且类型相同,就必须指定 key。例如,一个列表部件管理了多个列表项,就需要为列表中的每一项指定一个 key

当重新渲染 VDOM 中受影响部分时,Dojo 使用虚拟节点的 key 来唯一标识特定实例。如果没有使用 key 在 VDOM 中区分开同一层级中的相同类型的多个节点,则 Dojo 就无法准确地确定哪些子节点受到了失效更改(invalidating change)的影响。

注意: 虚拟节点的 key 应在多次渲染函数的调用中保持一致。在每一次的渲染调用中,为相同的输出节点生成不同的 key,在 Dojo 应用程序开发中被认为是反模式的,应当避免。

定义部件的 key

按惯例,Dojo 的渲染引擎在渲染时使用部件的 key 属性来唯一标识和跟踪部件。但是,为了确保 Dojo 渲染引擎在下一次渲染时重新创建部件,而不是重用之前的实例,更新 key 属性也是一种行之有效的方法。重新创建部件时,之前所有的状态都会被重置。当部件基于属性值来管理逻辑时,这种行为是非常有用的。

Dojo 为部件的开发者提供一种机制,通过使用 create() 工厂的链接方法 .key() 将部件的属性关联为部件的标识。

import { create } from '@dojo/framework/core/vdom';

interface MyWidgetProperties {
	id: string;
}

const factory = create()
	.properties<MyWidgetProperties>()
	.key('id');

使用这个 factory 后,当 id 属性值改变时,Dojo 将重新创建部件实例。这个强大的特性让部件开发者能轻松做到,当定义的属性值变更后就重新创建部件,因此开发者不需要处理基于属性来刷新数据的复杂逻辑。

import { create } from '@dojo/framework/core/vdom';
import icache from '@dojo/framework/core/middleware/icache';

interface MyWidgetProperties {
	id: string;
}

const factory = create({ icache })
	.properties<MyWidgetProperties>()
	.key('id');

const MyWidget = factory(function MyWidget({ properties, middleware: { icache } }) {
	const { id } = properties();
	const data = icache.getOrSet('data', async () => {
		const response = await fetch(`https://my-api/items/${id}`);
		const json = await response.json();
		return json.data;
	});

	if (!data) {
		return <div>Loading Data...</div>;
	}

	return (
		<div>
			<ul>{data.map((item) => <li>{item}</li>)}</ul>
		</div>
	);
});

本实例演示了基于 id 属性来获取数据。如果不使用 .key('id'),部件需要管理 id 属性变更逻辑。包括确定属性是否已真正修改,重新获取相关数据以及显示加载信息等。使用 .key('id') 能确保当 id 属性变化后,部件能重新创建,并重置所有状态,然后部件显示 “Loading Data...” 信息,并基于更新后的 id 获取数据(而不是使用 icache 缓存的数据)。

配置 VNode

VNodeProperties 包含很多字段,是与 DOM 中的元素交互的重要 API。其中很多属性镜像了 HTMLElement 中的可用属性,包括指定各种 oneventname 的事件处理器。

应用程序的这些属性是单向的,因为 Dojo 将给定的属性集应用到具体的 DOM 元素上,但不会将相应的 DOM 属性后续的任何更改同步到 VNodeProperties。任何此类更改都应该通过事件处理器回传给 Dojo 应用程序。当调用事件处理程序时,应用程序可以处理事件所需的任何状态更改,在输出 VDOM 结构进行渲染时,更新对应的 VNodeProperties 视图,然后 Dojo 的 Renderer 会同步所有相关的 DOM 更新

修改属性和差异检测

Dojo 使用虚拟节点的属性来确定给定节点是否已更新,从而是否需要重新渲染。具体来说,它使用差异检测策略来比较前一次和当前渲染帧的属性集。如果在节点接收的最新属性集中检测到差异,则该节点将失效,并在下一个绘制周期中重新渲染。

注意,在做属性差异检测时会忽略掉函数,因为常见模式是在每次渲染时都会实例化一个新的函数。考虑下面的示例,子部件 ChildWidget 会在每次渲染时接收一个新的 onClick 函数。

export const ParentWidget(function ParentWidget() {
  return <ChildWidget onClick={() => {
      console.log('child widget clicked.');
  }} />
});

如果在差异检测期间对函数进行检测,这将导致每次渲染完 ParentWidget 后都重新渲染 ChildWidget

注意: 属性更改检测是由框架内部管理的,依赖于在部件的渲染函数中声明的 VDOM 输出结构。试图保留属性的引用,并在正常的部件渲染周期之外对其进行修改,在 Dojo 应用程序开发中被视为反模式的,应当避免。