dojo 龙主logo

管理状态

对于数据不需要在多个组件之间流动的简单应用程序,状态管理非常简单。数据可以封装在需要它的单个小部件中,作为 Dojo 应用程序中最基本的状态管理形式

随着应用程序变得越来越复杂,并开始需要在多个小部件之间共享和传输数据,需要更强大的状态管理形式。在这里,Dojo 开始证明其作为反应式框架的价值,允许应用程序定义数据如何在组件之间流动,然后让框架管理更改检测和重新渲染。这是通过将小部件和属性连接在一起在小部件的渲染函数中声明 VDOM 输出时完成的。

对于大型应用程序,状态管理可能是最具挑战性的方面之一,需要开发人员在数据一致性、可用性和容错性之间取得平衡。虽然很多复杂性仍然超出了 Web 应用程序层的范围,但 Dojo 提供了进一步的解决方案来帮助确保数据一致性。Dojo 存储组件提供了一个集中式状态存储,具有用于从应用程序中多个位置访问和管理数据的统一 API。

基本:自封装小部件状态

小部件可以通过多种方式维护自己的内部状态。基于函数的小部件可以使用icache中间件来存储小部件本地状态,而基于类的的小部件可以使用内部类字段。

内部状态数据可能会直接影响小部件的渲染输出,或者可能会作为属性传递给任何子小部件,这些子小部件反过来会直接影响子小部件的渲染输出。小部件还可以允许更改其内部状态,例如响应用户交互事件。

以下示例说明了这些模式

src/widgets/MyEncapsulatedStateWidget.tsx

基于函数的变体

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

const factory = create({ icache });

export default factory(function MyEncapsulatedStateWidget({ middleware: { icache } }) {
	return (
		<div>
			Current widget state: {icache.get<string>('myState') || 'Hello from a stateful widget!'}
			<br />
			<button
				onclick={() => {
					let counter = icache.get<number>('counter') || 0;
					let myState = 'State change iteration #' + ++counter;
					icache.set('myState', myState);
					icache.set('counter', counter);
				}}
			>
				Change State
			</button>
		</div>
	);
});

基于类的变体

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

export default class MyEncapsulatedStateWidget extends WidgetBase {
	private myState = 'Hello from a stateful widget!';
	private counter = 0;

	protected render() {
		return (
			<div>
				Current widget state: {this.myState}
				<br />
				<button
					onclick={() => {
						this.myState = 'State change iteration #' + ++this.counter;
					}}
				>
					Change State
				</button>
			</div>
		);
	}
}

请注意,此示例并不完整 - 在运行的应用程序中单击“更改状态”按钮不会对小部件的渲染输出有任何影响。这是因为状态完全封装在 MyEncapsulatedStateWidget 中,而 Dojo 不知道对其进行的任何更改。只有小部件的初始渲染将由框架处理。

为了通知 Dojo 需要重新渲染,封装渲染状态的小部件需要使自己失效。

使小部件失效

基于函数的小部件可以使用icache 中间件来处理本地状态管理,该中间件在状态更新时会自动使小部件失效。icache 组合了cacheinvalidator 中间件,其中 cache 处理小部件状态管理,而 invalidator 处理状态更改时的小部件失效。如果需要,基于函数的小部件也可以直接使用 invalidator

对于基于类的的小部件,有两种使失效的方法

  1. 在适当的位置显式调用 this.invalidate(),在该位置更改状态。
    • MyEncapsulatedStateWidget 示例中,这可以在“更改状态”按钮的 onclick 处理程序中完成。
  2. 使用 @watch() 装饰器(来自 @dojo/framework/core/vdomecorators/watch 模块)注释任何相关字段。当修改 @watched 字段时,this.invalidate() 将被隐式调用 - 这对于始终需要在更新时触发重新渲染的状态字段很有用。

注意:将小部件标记为无效不会立即重新渲染小部件 - 相反,它充当对 Dojo 的通知,表明小部件处于脏状态,应该在下一个渲染周期中更新和重新渲染。这意味着在同一个渲染帧内多次使小部件失效不会对应用程序性能产生负面影响,尽管应避免过度失效以确保最佳性能。

以下是更新后的 MyEncapsulatedStateWidget 示例,它将在更改其状态时正确更新其输出。

基于函数的变体

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

const factory = create({ icache });

export default factory(function MyEncapsulatedStateWidget({ middleware: { icache } }) {
	return (
		<div>
			Current widget state: {icache.getOrSet<string>('myState', 'Hello from a stateful widget!')}
			<br />
			<button
				onclick={() => {
					let counter = icache.get<number>('counter') || 0;
					let myState = 'State change iteration #' + ++counter;
					icache.set('myState', myState);
					icache.set('counter', counter);
				}}
			>
				Change State
			</button>
		</div>
	);
});

基于类的变体

在这里,myStatecounter 都是作为同一个应用程序逻辑操作的一部分更新的,因此可以在这两个字段中的任何一个或两个字段中添加 @watch(),在所有情况下都具有相同的净效果和性能配置文件

src/widgets/MyEncapsulatedStateWidget.tsx

import WidgetBase from '@dojo/framework/core/WidgetBase';
import watch from '@dojo/framework/core/decorators/watch';
import { tsx } from '@dojo/framework/core/vdom';

export default class MyEncapsulatedStateWidget extends WidgetBase {
	private myState: string = 'Hello from a stateful widget!';

	@watch() private counter: number = 0;

	protected render() {
		return (
			<div>
				Current widget state: {this.myState}
				<br />
				<button
					onclick={() => {
						this.myState = 'State change iteration #' + ++this.counter;
					}}
				>
					Change State
				</button>
			</div>
		);
	}
}

中级:传递小部件属性

通过虚拟节点properties 将状态传递到小部件是连接 Dojo 应用程序中反应式数据流的最有效方法。

小部件指定自己的属性接口,其中可以包含小部件想要公开宣传给消费者的任何字段,包括配置选项、表示可注入状态的字段,以及任何事件处理程序函数。

基于函数的小部件将其属性接口作为泛型类型参数传递给 create().properties<MyPropertiesInterface>() 调用。从这个调用链返回的工厂然后通过渲染函数定义中的 properties 函数参数使属性值可用。

基于类的的小部件可以将其属性接口定义为其类定义中 WidgetBase 的泛型类型参数,然后通过 this.properties 对象访问其属性。

例如,一个小部件支持状态和事件处理程序属性

src/widgets/MyWidget.tsx

基于函数的变体

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

const factory = create().properties<{
	name: string;
	onNameChange?(newName: string): void;
}>();

export default factory(function MyWidget({ middleware: { icache }, properties }) {
	const { name, onNameChange } = properties();
	let newName = icache.get<string>('new-name') || '';
	return (
		<div>
			<span>Hello, {name}! Not you? Set your name:</span>
			<input
				type="text"
				value={newName}
				oninput={(e: Event) => {
					icache.set('new-name', (e.target as HTMLInputElement).value);
				}}
			/>
			<button
				onclick={() => {
					icache.set('new-name', undefined);
					onNameChange && onNameChange(newName);
				}}
			>
				Set new name
			</button>
		</div>
	);
});

基于类的变体

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

export interface MyWidgetProperties {
	name: string;
	onNameChange?(newName: string): void;
}

export default class MyWidget extends WidgetBase<MyWidgetProperties> {
	private newName = '';
	protected render() {
		const { name, onNameChange } = this.properties;
		return (
			<div>
				<span>Hello, {name}! Not you? Set your name:</span>
				<input
					type="text"
					value={this.newName}
					oninput={(e: Event) => {
						this.newName = (e.target as HTMLInputElement).value;
						this.invalidate();
					}}
				/>
				<button
					onclick={() => {
						this.newName = '';
						onNameChange && onNameChange(newName);
					}}
				>
					Set new name
				</button>
			</div>
		);
	}
}

此示例小部件的使用者可以通过传入适当的属性与它进行交互

src/widgets/NameHandler.tsx

基于函数的变体

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

import MyWidget from './MyWidget';

const factory = create({ icache });

export default factory(function NameHandler({ middleware: { icache } }) {
	let currentName = icache.get<string>('current-name') || 'Alice';
	return (
		<MyWidget
			name={currentName}
			onNameChange={(newName) => {
				icache.set('current-name', newName);
			}}
		/>
	);
});

基于类的变体

import WidgetBase from '@dojo/framework/core/WidgetBase';
import { tsx } from '@dojo/framework/core/vdom';
import watch from '@dojo/framework/core/decorators/watch';
import MyWidget from './MyWidget';

export default class NameHandler extends WidgetBase {
	@watch() private currentName: string = 'Alice';

	protected render() {
		return (
			<MyWidget
				name={this.currentName}
				onNameChange={(newName) => {
					this.currentName = newName;
				}}
			/>
		);
	}
}

高级:抽象和注入状态

在实现复杂职责时,遵循在小部件中封装状态的模式会导致臃肿、难以管理的组件。另一个问题可能出现在具有数百个小部件的跨数十层结构层次结构的大型应用程序中。状态通常需要在叶小部件中,但在 VDOM 层次结构中的中间容器中不需要。通过所有层级的这种复杂的小部件层次结构传递状态会增加脆弱、不必要的代码。

Dojo 提供了存储组件 来解决这些问题,方法是将状态管理抽象到其自己的专用上下文中,然后将应用程序状态的相关部分注入到需要它的特定小部件中。