State
对象在现代浏览器中,state
对象是做为 CommandRequest
的一部分传入的。对 state
对象的任何修改都将转换为相应的 operation,而后应用到 store 上。git
import { createCommandFactory } from '@dojo/framework/stores/process'; import { State } from './interfaces'; import { remove, replace } from '@dojo/framework/stores/state/operations'; const createCommand = createCommandFactory<State>(); const addUser = createCommand<User>(({ payload, state }) => { const currentUsers = state.users.list || []; state.users.list = [...currentUsers, payload]; });
注意,IE 11 不支持访问 state,若是尝试访问将当即抛出错误。github
StoreProvider
StoreProvider 接收三个属性编程
renderer
: 一个渲染函数,已将 store 注入其中,能访问状态并向子部件传入 process。stateKey
: 注册状态时使用的 key 值。paths
(可选): 将此 provider 链接到状态的某一局部上。StoreProvider
有两种方法触发失效并促使从新渲染。json
paths
属性来注册 path
,以确保只有相关状态变化时才会失效。path
时,store 中的 任何 数据变化都会引发失效。Process
Process
有一个执行生命周期,它定义了所定义行为的流程。后端
before
中间件after
中间件使用可选的 before
和 after
方法在 process 的先后应用中间件。这容许在 process 所定义行为的前和后加入通用的、可共享的操做。数组
也能够在列表中定义多个中间件。会根据中间件在列表中的顺序同步调用。浏览器
before
中间件块能获取传入的 payload
和 store
的引用。安全
middleware/beforeLogger.ts
const beforeOnly: ProcessCallback = () => ({ before(payload, store) { console.log('before only called'); } });
after
中间件块能获取传入的 error
(若是发生了错误的话)和 process 的 result
。服务器
middleware/afterLogger.ts
const afterOnly: ProcessCallback = () => ({ after(error, result) { console.log('after only called'); } });
result
实现了 ProcessResult
接口,以提供有关应用到 store 上的变动信息和提供对 store 的访问。并发
executor
- 容许在 store 上运行其余 processstore
- store 引用operations
- 一组应用的 operationundoOperations
- 一组 operation,用来撤销所应用的 operationapply
- store 上的 apply 方法payload
- 提供的 payloadid
- 用于命名 process 的 idStore
有一个 onChange(path, callback)
方法,该方法接收一个或一组 path,并在状态变动时调用回调函数。
main.ts
const store = new Store<State>(); const { path } = store; store.onChange(path('auth', 'token'), () => { console.log('new login'); }); store.onChange([path('users', 'current'), path('users', 'list')], () => { // Make sure the current user is in the user list });
Store
中还有一个 invalidate
事件,store 变化时就触发该事件。
main.ts
store.on('invalidate', () => { // do something when the store's state has been updated. });
首次建立 store 时,它为空。而后,可使用一个 process 为 store 填充初始的应用程序状态。
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 store 使用 patch operation 跟踪底层 store 的变化。这样,Dojo 就很容易建立一组 operation,而后撤销这组 operation,以恢复一组 command 所修改的任何数据。undoOperations
是 ProcessResult
的一部分,可在 after
中间件中使用。
当一个 process 包含了多个修改 store 状态的 command,而且其中一个 command 执行失败,须要回滚时,撤销(Undo) operation 很是有用。
undo middleware
const undoOnFailure = () => { return { after: () => (error, result) { if (error) { result.store.apply(result.undoOperations); } } }; }; const process = createProcess('do-something', [ command1, command2, command3 ], [ undoOnFailure ])
在执行时,任何 command 出错,则 undoOnFailure
中间件就负责应用 undoOperations
。
须要注意的是,undoOperations
仅适用于在 process 中彻底执行的 command。在回滚状态时,它将不包含如下任何 operation,这些状态的变动多是异步执行的其余 process 引发的,或者在中间件中执行的状态变动,或者直接在 store 上操做的。这些用例不在 undo 系统的范围内。
乐观更新可用于构建响应式 UI,尽管交互可能须要一些时间才能响应,例如往远程保存资源。
例如,假使正在添加一个 todo 项,经过乐观更新,能够在向服务器发送持久化对象的请求以前,就将 todo 项添加到 store 中,从而避免尴尬的等待期或者加载指示器。当服务器响应后,能够根据服务器操做的结果成功与否,来协调 store 中的 todo 项。
在成功的场景中,使用服务器响应中提供的 id
来更新已添加的 Todo
项,并将 Todo
项的颜色改成绿色,以指示已保存成功。
在出错的场景中,能够显示一个通知,说明请求失败,并将 Todo
项的颜色改成红色,同时显示一个“重试”按钮。甚至能够恢复或撤销添加的 Todo 项,以及在 process 中发生的其余任何操做。
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
- 在应用程序状态中添加一个 todo 项calculateCountsCommand
- 从新计算已完成的待办项个数和活动的待办项个数postTodoCommand
- 将 todo 项提交给远程服务,并使用 process 的 after
中间件在发生错误时执行进一步更改
calculateCountsCommand
- postTodoCommand
成功后再运行一次在某些状况下,在继续执行 process 以前,最好等后端调用完成。例如,当 process 从屏幕中删除一个元素时,或者 outlet 发生变化要显示不一样的视图,恢复触发这些操做的状态可能会让人感到很诡异(译注:数据先从界面上删掉了,由于后台删除失败,过一会数据又出如今界面上)。
由于 process 支持异步 command,只需简单的返回 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
支持并发执行多个 command,只需将这些 command 放在一个数组中便可。
process.ts
createProcess('my-process', [commandLeft, [concurrentCommandOne, concurrentCommandTwo], commandRight]);
本示例中,commandLeft
先执行,而后并发执行 concurrentCommandOne
和 concurrentCommandTwo
。当全部的并发 command 执行完成后,就按需应用返回的结果。若是任一并发 command 出错,则不会应用任何操做。最后,执行 commandRight
。
当实例化 store 时,会默认使用 MutableState
接口的实现。在大部分状况下,默认的状态接口都通过了很好的优化,足以适用于常见状况。若是一个特殊的用例须要另外一个实现,则能够在初始化时传入该实现。
const store = new Store({ state: myStateImpl });
MutableState
API任何 State
实现都必须提供四个方法,以在状态上正确的应用操做。
get<S>(path: Path<M, S>): S
接收一个 Path
对象,并返回当前状态中该 path 指向的值at<S extends Path<M, Array<any>>>(path: S, index: number): Path<M, S['value'][0]>
返回一个 Path
对象,该对象指向 path 定位到的数组中索引为 index
的值path: StatePaths<M>
以类型安全的方式,为状态中给定的 path 生成一个 Path
对象apply(operations: PatchOperation<T>[]): PatchOperation<T>[]
将提供的 operation 应用到当前状态上ImmutableState
Dojo Store 经过 Immutable 为 MutableState 接口提供了一个实现。若是对 store 的状态作频繁的、较深层级的更新,则这个实现可能会提升性能。在最终决定使用这个实现以前,应先测试和验证性能。
Using 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 Store 提供了一组工具来使用本地存储(local storage)。
本地存储中间件监视指定路径上的变化,并使用 collector
中提供的 id
和 path 中定义的结构,将它们存储在本地磁盘上。
使用本地存储中间件:
export const myProcess = createProcess( 'my-process', [command], collector('my-process', (path) => { return [path('state', 'to', 'save'), path('other', 'state', 'to', 'save')]; }) );
来自 LocalStorage
中的 load
函数用于与 store 结合
与状态结合:
import { load } from '@dojo/framework/stores/middleware/localStorage'; import { Store } from '@dojo/framework/stores/Store'; const store = new Store(); load('my-process', store);
注意,数据要可以被序列化以便存储,并在每次调用 process 后都会覆盖数据。此实现不适用于不能序列化的数据(如 Date
和 ArrayBuffer
)。
Dojo Store 使用 operation 来更改应用程序的底层状态。这样设计 operation,有助于简化对 store 的经常使用交互,例如,operation 将自动建立支持 add
或 replace
operation 所需的底层结构。
在未初始化的 store 中执行一个深度 add
:
import Store from '@dojo/framework/stores/Store'; import { add } from '@dojo/framework/stores/state/operations'; const store = new Store<State>(); const { at, path, apply } = store; const user = { id: '0', name: 'Paul' }; apply([add(at(path('users', 'list'), 10), user)]);
结果为:
{ "users": { "list": [ { "id": "0", "name": "Paul" } ] } }
即便状态还没有初始化,Dojo 也能基于提供的 path 建立出底层的层次结构。这个操做是安全的,由于 TypeScript 和 Dojo 提供了类型安全。这容许用户很天然的使用 store 所用的 State
接口,而不须要显式关注 store 中保存的数据。
当须要显式使用数据时,可使用 test
操做或者经过获取底层数据来断言该信息,并经过编程的方式来验证。
本示例使用 test
操做来确保已初始化,确保始终将 user
添加到列表的末尾:
import Store from '@dojo/framework/stores/Store'; import { test } from '@dojo/framework/stores/state/operations'; const store = new Store<State>(); const { at, path, apply } = store; apply([test(at(path('users', 'list', 'length'), 0))]);
本示例经过编程的方式,确保 user
老是做为最后一个元素添加到列表的末尾:
import Store from '@dojo/framework/stores/Store'; import { add, test } from '@dojo/framework/stores/state/operations'; const store = new Store<State>(); const { get, at, path, apply } = store; const user = { id: '0', name: 'Paul' }; const pos = get(path('users', 'list', 'length')) || 0; apply([ add(at(path('users', 'list'), pos), user), test(at(path('users', 'list'), pos), user), test(path('users', 'list', 'length'), pos + 1) ]);
禁止访问状态的根节点,若是访问将会引起错误,例如尝试执行 get(path('/'))
。此限制也适用于 operation;不能建立一个更新状态根节点的 operation。@dojo/framewok/stores
的最佳实践是鼓励只访问 store 中最小的、必需的部分。