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 中最小的、必需的部分。