Dojo Store 概念詳解
State物件
在現代瀏覽器中,state物件是作為CommandRequest的一部分傳入的。對state物件的任何修改都將轉換為相應的 operation,然後應用到 store 上。
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,如果嘗試訪問將立即丟擲錯誤。
StoreProvider
StoreProvider 接收三個屬性
- renderer: 一個渲染函式,已將 store 注入其中,能訪問狀態並向子部件傳入 process。
- stateKey: 註冊狀態時使用的 key 值。
- paths(可選): 將此 provider 連線到狀態的某一區域性上。
失效
StoreProvider有兩種方法觸發失效並促使重新渲染。
- 推薦的方式是,通過向 provider 傳入paths屬性來註冊path,以確保只有相關狀態變化時才會失效。
- 另一種是較籠統的方式,當沒有為 provider 定義path時,store 中的任何資料變化都會引起失效。
Process
生命週期
Process有一個執行生命週期,它定義了所定義行為的流程。
- 如果存在轉換器,則首先執行轉換器來轉換 payload 物件
- 按順序同步執行before中介軟體
- 按順序執行定義的 command
- 在執行完每個 command (如果是多個 command 則是一塊 command)之後,應用命令返回的 operation
- 如果在執行命令期間丟擲了異常,則不會再執行後續命令,並且也不會應用當前的 operation
- 按順序同步執行after中介軟體
Process 中介軟體
使用可選的before和after方法在 process 的前後應用中介軟體。這允許在 process 所定義行為的前和後加入通用的、可共享的操作。
也可以在列表中定義多箇中間件。會根據中介軟體在列表中的順序同步呼叫。
Before
before中介軟體塊能獲取傳入的payload和store的引用。
middleware/beforeLogger.ts
const beforeOnly: ProcessCallback = () => ({
before(payload, store) {
console.log('before only called');
}
});
After
after中介軟體塊能獲取傳入的error(如果發生了錯誤的話)和 process 的result。
middleware/afterLogger.ts
const afterOnly: ProcessCallback = () => ({
after(error, result) {
console.log('after only called');
}
});
result實現了ProcessResult介面,以提供有關應用到 store 上的變更資訊和提供對 store 的訪問。
- executor- 允許在 store 上執行其他 process
- store- store 引用
- operations- 一組應用的 operation
- undoOperations- 一組 operation,用來撤銷所應用的 operation
- apply- store 上的 apply 方法
- payload- 提供的 payload
- id- 用於命名 process 的 id
訂閱 store 的變化
Store有一個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)({});
Undo
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中介軟體在發生錯誤時執行進一步更改
- 失敗時將恢復更改,並將 failed 狀態欄位設定為 true
- 成功時使用從遠端服務返回的值更新 todo 項的 id 欄位
- 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]);
併發 command
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 });
MutableStateAPI
任何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 });
廣州品牌設計公司https://www.houdianzi.com PPT模板下載大全https://redbox.wode007.com
本地儲存
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)。
高階的 store operation
Dojo Store 使用 operation 來更改應用程式的底層狀態。這樣設計 operation,有助於簡化對 store 的常用互動,例如,operation 將自動建立支援add或replaceoperation 所需的底層結構。
在未初始化的 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)
]);