dojo 龙主要 logo

常见的状态管理模式

初始状态

当存储首次创建时,它将为空。然后可以使用一个过程来使用初始应用程序状态填充存储。

main.ts

const store = new Store<State>();
const { path } = store;

const createCommand = createCommandFactory<State>();

const initialStateCommand = createCommand(({ path }) => {
	return [add(path('auth'), { token: undefined }), add(path('users'), { list: [] })];
});

const initialStateProcess = createProcess('initial', [initialStateCommand]);

initialStateProcess(store)({});

撤销

Dojo 存储使用补丁操作跟踪对底层存储的更改。这使得 Dojo 很容易自动创建一组操作来撤销一组操作,并恢复作为一组命令的一部分而更改的任何数据。undoOperationsafter 中间件中作为 ProcessResult 的一部分提供。

当一个过程涉及多个更改存储状态的命令,并且其中一个命令失败,需要回滚时,撤销操作很有用。

撤销中间件

const undoOnFailure = () => {
	return {
		after: (error, result) => {
			if (error) {
				result.store.apply(result.undoOperations);
			}
		}
	};
};

const process = createProcess('do-something', [command1, command2, command3], [undoOnFailure]);

如果任何命令在执行期间失败,undoOnFailure 中间件将有机会应用 undoOperations

重要的是要注意,undoOperations 仅适用于在过程期间完全执行的命令。它不会包含任何操作来回滚由于可能异步执行的其他过程或在中间件或直接在存储本身执行的状态更改而导致的状态更改。这些用例超出了撤销系统的范围。

乐观更新

乐观更新可用于构建响应式 UI,即使交互可能需要一些时间才能响应,例如保存到远程资源。

例如,在添加待办事项的情况下,使用乐观更新,可以在向服务器发出持久化对象的请求之前立即将待办事项添加到存储中,从而避免不自然的等待时间或加载指示器。当服务器响应时,存储中的待办事项项可以根据服务器操作的结果是成功还是失败来进行协调。

在成功的情况下,添加的 Todo 项可以使用服务器响应中提供的 id 进行更新,并且 Todo 项的颜色可以更改为绿色以指示它已成功保存。

在错误的情况下,可以显示一条通知,说明请求失败,并且待办事项项的颜色可以更改为红色,同时显示一个“重试”按钮。甚至可以恢复/撤销待办事项项的添加或在过程期间发生的任何其他操作。

const handleAddTodoErrorProcess = createProcess('error', [ () => [ add(path('failed'), true) ]; ]);

const addTodoErrorMiddleware = () => {
	return {
		after: () => (error, result) {
			if (error) {
				result.store.apply(result.undoOperations);
				result.executor(handleAddTodoErrorProcess);
			}
		}
	};
};

const addTodoProcess = createProcess('add-todo', [
		addTodoCommand,
		calculateCountsCommand,
		postTodoCommand,
		calculateCountsCommand
	],
	[ addTodoCallback ]);
  • addTodoCommand - 将新的待办事项添加到应用程序状态中
  • calculateCountsCommand - 重新计算已完成和活动待办事项项的数量
  • postTodoCommand - 将待办事项项发布到远程服务,并使用过程 after 中间件,如果发生故障,可以进行进一步的更改
    • 失败的情况下,更改将被恢复,并且失败状态字段将设置为 true
    • 成功的情况下,使用从远程服务接收的值更新待办事项项 id 字段
  • calculateCountsCommand - 在 postTodoCommand 成功后再次运行

同步更新

在某些情况下,最好等待后端调用完成,然后再继续执行过程。例如,当一个过程将从屏幕上删除一个元素,或者当出口更改为显示不同的视图时,恢复触发这些操作的状态可能会令人惊讶。

由于过程支持异步命令,只需返回一个 Promise 来等待结果。

function byId(id: string) {
	return (item: any) => id === item.id;
}

async function deleteTodoCommand({ get, payload: { id } }: CommandRequest) {
	const { todo, index } = find(get('/todos'), byId(id));
	await fetch(`/todo/${todo.id}`, { method: 'DELETE' });
	return [remove(path('todos', index))];
}

const deleteTodoProcess = createProcess('delete', [deleteTodoCommand, calculateCountsCommand]);

并发命令

一个 Process 通过在数组中指定命令来支持多个命令的并发执行。

process.ts

createProcess('my-process', [commandLeft, [concurrentCommandOne, concurrentCommandTwo], commandRight]);

在这个例子中,commandLeft 被执行,然后 concurrentCommandOneconcurrentCommandTwo 并发执行。一旦所有并发命令完成,结果将按顺序应用。如果任何并发命令中发生错误,则不会应用任何操作。最后,commandRight 被执行。

替代状态实现

当存储被实例化时,MutableState 接口的实现默认情况下被使用。在大多数情况下,默认状态接口经过良好优化,足以用于一般情况。如果特定用例需要替代实现,则可以在初始化期间传递该实现。

const store = new Store({ state: myStateImpl });

MutableState API

任何 State 实现都必须提供四种方法才能正确地将操作应用于状态。

  • get<S>(path: Path<M, S>): S 接受一个 Path 对象,并返回当前状态中提供的路径的值
  • at<S extends Path<M, Array<any>>>(path: S, index: number): Path<M, S['value'][0]> 返回一个 Path 对象,该对象指向提供的路径中数组的提供的 index
  • path: StatePaths<M> 是一种类型安全的方式,可以为状态中的给定路径生成一个 Path 对象
  • apply(operations: PatchOperation<T>[]): PatchOperation<T>[] 将提供的操作应用于当前状态

ImmutableState

Dojo 存储提供了一个 MutableState 接口的实现,该实现利用了 Immutable。如果存储状态的频繁、深度更新,这种实现可能会提供更好的性能。在完全采用这种实现之前,应该测试和验证性能。

使用 Immutable

import State from './interfaces';
import Store from '@dojo/framework/stores/Store';
import Registry from '@dojo/framework/widget-core/Registry';
import ImmutableState from '@dojo/framework/stores/state/ImmutableState';

const registry = new Registry();
const customStore = new ImmutableState<State>();
const store = new Store<State>({ store: customStore });

本地存储

Dojo 存储提供了一组工具来利用本地存储。

本地存储中间件监视指定路径的更改,并使用提供给 collectorid 和路径定义的结构将它们存储在磁盘上的本地位置。

使用本地存储中间件

export const myProcess = createProcess(
	'my-process',
	[command],
	collector('my-process', (path) => {
		return [path('state', 'to', 'save'), path('other', 'state', 'to', 'save')];
	})
);

load 函数从 LocalStorage 中提取存储。

要提取状态

import { load } from '@dojo/framework/stores/middleware/localStorage';
import { Store } from '@dojo/framework/stores/Store';

const store = new Store();
load('my-process', store);

请注意,数据被序列化以进行存储,并且数据在每次过程调用后都会被覆盖。这种实现不适合非可序列化数据(例如 DateArrayBuffer)。