溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Dojo Store 概念詳解

發(fā)布時(shí)間:2020-06-16 20:52:10 來源:網(wǎng)絡(luò) 閱讀:234 作者:blocklang 欄目:web開發(fā)

翻譯自:https://github.com/dojo/framework/blob/master/docs/en/stores/supplemental.md

State 對(duì)象

在現(xiàn)代瀏覽器中,state 對(duì)象是作為 CommandRequest 的一部分傳入的。對(duì) state 對(duì)象的任何修改都將轉(zhuǎn)換為相應(yīng)的 operation,然后應(yīng)用到 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,如果嘗試訪問將立即拋出錯(cuò)誤。

StoreProvider

StoreProvider 接收三個(gè)屬性

  • renderer: 一個(gè)渲染函數(shù),已將 store 注入其中,能訪問狀態(tài)并向子部件傳入 process。
  • stateKey: 注冊狀態(tài)時(shí)使用的 key 值。
  • paths (可選): 將此 provider 連接到狀態(tài)的某一局部上。

失效

StoreProvider 有兩種方法觸發(fā)失效并促使重新渲染。

  1. 推薦的方式是,通過向 provider 傳入 paths 屬性來注冊 path,以確保只有相關(guān)狀態(tài)變化時(shí)才會(huì)失效。
  2. 另一種是較籠統(tǒng)的方式,當(dāng)沒有為 provider 定義 path 時(shí),store 中的 任何 數(shù)據(jù)變化都會(huì)引起失效。

Process

生命周期

Process 有一個(gè)執(zhí)行生命周期,它定義了所定義行為的流程。

  1. 如果存在轉(zhuǎn)換器,則首先執(zhí)行轉(zhuǎn)換器來轉(zhuǎn)換 payload 對(duì)象
  2. 按順序同步執(zhí)行 before 中間件
  3. 按順序執(zhí)行定義的 command
  4. 在執(zhí)行完每個(gè) command (如果是多個(gè) command 則是一塊 command)之后,應(yīng)用命令返回的 operation
  5. 如果在執(zhí)行命令期間拋出了異常,則不會(huì)再執(zhí)行后續(xù)命令,并且也不會(huì)應(yīng)用當(dāng)前的 operation
  6. 按順序同步執(zhí)行 after 中間件

Process 中間件

使用可選的 beforeafter 方法在 process 的前后應(yīng)用中間件。這允許在 process 所定義行為的前和后加入通用的、可共享的操作。

也可以在列表中定義多個(gè)中間件。會(huì)根據(jù)中間件在列表中的順序同步調(diào)用。

Before

before 中間件塊能獲取傳入的 payloadstore 的引用。

middleware/beforeLogger.ts

const beforeOnly: ProcessCallback = () => ({
    before(payload, store) {
        console.log('before only called');
    }
});
After

after 中間件塊能獲取傳入的 error (如果發(fā)生了錯(cuò)誤的話)和 process 的 result

middleware/afterLogger.ts

const afterOnly: ProcessCallback = () => ({
    after(error, result) {
        console.log('after only called');
    }
});

result 實(shí)現(xiàn)了 Proce***esult 接口,以提供有關(guān)應(yīng)用到 store 上的變更信息和提供對(duì) store 的訪問。

  • executor - 允許在 store 上運(yùn)行其他 process
  • store - store 引用
  • operations - 一組應(yīng)用的 operation
  • undoOperations - 一組 operation,用來撤銷所應(yīng)用的 operation
  • apply - store 上的 apply 方法
  • payload - 提供的 payload
  • id - 用于命名 process 的 id

訂閱 store 的變化

Store 有一個(gè) onChange(path, callback) 方法,該方法接收一個(gè)或一組 path,并在狀態(tài)變更時(shí)調(diào)用回調(diào)函數(shù)。

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 中還有一個(gè) invalidate 事件,store 變化時(shí)就觸發(fā)該事件。

main.ts

store.on('invalidate', () => {
    // do something when the store's state has been updated.
});

共享的狀態(tài)管理模式

初始狀態(tài)

首次創(chuàng)建 store 時(shí),它為空。然后,可以使用一個(gè) process 為 store 填充初始的應(yīng)用程序狀態(tài)。

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 就很容易創(chuàng)建一組 operation,然后撤銷這組 operation,以恢復(fù)一組 command 所修改的任何數(shù)據(jù)。undoOperationsProce***esult 的一部分,可在 after 中間件中使用。

當(dāng)一個(gè) process 包含了多個(gè)修改 store 狀態(tài)的 command,并且其中一個(gè) command 執(zhí)行失敗,需要回滾時(shí),撤銷(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 ])

在執(zhí)行時(shí),任何 command 出錯(cuò),則 undoOnFailure 中間件就負(fù)責(zé)應(yīng)用 undoOperations。

需要注意的是,undoOperations 僅適用于在 process 中完全執(zhí)行的 command。在回滾狀態(tài)時(shí),它將不包含以下任何 operation,這些狀態(tài)的變更可能是異步執(zhí)行的其他 process 引起的,或者在中間件中執(zhí)行的狀態(tài)變更,或者直接在 store 上操作的。這些用例不在 undo 系統(tǒng)的范圍內(nèi)。

樂觀更新

樂觀更新可用于構(gòu)建響應(yīng)式 UI,盡管交互可能需要一些時(shí)間才能響應(yīng),例如往遠(yuǎn)程保存資源。

例如,假使正在添加一個(gè) todo 項(xiàng),通過樂觀更新,可以在向服務(wù)器發(fā)送持久化對(duì)象的請(qǐng)求之前,就將 todo 項(xiàng)添加到 store 中,從而避免尷尬的等待期或者加載指示器。當(dāng)服務(wù)器響應(yīng)后,可以根據(jù)服務(wù)器操作的結(jié)果成功與否,來協(xié)調(diào) store 中的 todo 項(xiàng)。

在成功的場景中,使用服務(wù)器響應(yīng)中提供的 id 來更新已添加的 Todo 項(xiàng),并將 Todo 項(xiàng)的顏色改為綠色,以指示已保存成功。

在出錯(cuò)的場景中,可以顯示一個(gè)通知,說明請(qǐng)求失敗,并將 Todo 項(xiàng)的顏色改為紅色,同時(shí)顯示一個(gè)“重試”按鈕。甚至可以恢復(fù)或撤銷添加的 Todo 項(xiàng),以及在 process 中發(fā)生的其他任何操作。

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 - 在應(yīng)用程序狀態(tài)中添加一個(gè) todo 項(xiàng)
  • calculateCountsCommand - 重新計(jì)算已完成的待辦項(xiàng)個(gè)數(shù)和活動(dòng)的待辦項(xiàng)個(gè)數(shù)
  • postTodoCommand - 將 todo 項(xiàng)提交給遠(yuǎn)程服務(wù),并使用 process 的 after 中間件在發(fā)生錯(cuò)誤時(shí)執(zhí)行進(jìn)一步更改
    • 失敗時(shí) 將恢復(fù)更改,并將 failed 狀態(tài)字段設(shè)置為 true
    • 成功時(shí) 使用從遠(yuǎn)程服務(wù)返回的值更新 todo 項(xiàng)的 id 字段
  • calculateCountsCommand - postTodoCommand 成功后再運(yùn)行一次

同步更新

在某些情況下,在繼續(xù)執(zhí)行 process 之前,最好等后端調(diào)用完成。例如,當(dāng) process 從屏幕中刪除一個(gè)元素時(shí),或者 outlet 發(fā)生變化要顯示不同的視圖,恢復(fù)觸發(fā)這些操作的狀態(tài)可能會(huì)讓人感到很詭異(譯注:數(shù)據(jù)先從界面上刪掉了,因?yàn)楹笈_(tái)刪除失敗,過一會(huì)數(shù)據(jù)又出現(xiàn)在界面上)。

因?yàn)?process 支持異步 command,只需簡單的返回 Promise 以等待結(jié)果。

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]);

并發(fā) command

Process 支持并發(fā)執(zhí)行多個(gè) command,只需將這些 command 放在一個(gè)數(shù)組中即可。

process.ts

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

本示例中,commandLeft 先執(zhí)行,然后并發(fā)執(zhí)行 concurrentCommandOneconcurrentCommandTwo。當(dāng)所有的并發(fā) command 執(zhí)行完成后,就按需應(yīng)用返回的結(jié)果。如果任一并發(fā) command 出錯(cuò),則不會(huì)應(yīng)用任何操作。最后,執(zhí)行 commandRight。

可替換的狀態(tài)實(shí)現(xiàn)

當(dāng)實(shí)例化 store 時(shí),會(huì)默認(rèn)使用 MutableState 接口的實(shí)現(xiàn)。在大部分情況下,默認(rèn)的狀態(tài)接口都經(jīng)過了很好的優(yōu)化,足以適用于常見情況。如果一個(gè)特殊的用例需要另一個(gè)實(shí)現(xiàn),則可以在初始化時(shí)傳入該實(shí)現(xiàn)。

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

MutableState API

任何 State 實(shí)現(xiàn)都必須提供四個(gè)方法,以在狀態(tài)上正確的應(yīng)用操作。

  • get&lt;S&gt;(path: Path&lt;M, S&gt;): S 接收一個(gè) Path 對(duì)象,并返回當(dāng)前狀態(tài)中該 path 指向的值
  • at&lt;S extends Path&lt;M, Array&lt;any&gt;&gt;&gt;(path: S, index: number): Path&lt;M, S['value'][0]&gt; 返回一個(gè) Path 對(duì)象,該對(duì)象指向 path 定位到的數(shù)組中索引為 index 的值
  • path: StatePaths&lt;M&gt; 以類型安全的方式,為狀態(tài)中給定的 path 生成一個(gè) Path 對(duì)象
  • apply(operations: PatchOperation&lt;T&gt;[]): PatchOperation&lt;T&gt;[] 將提供的 operation 應(yīng)用到當(dāng)前狀態(tài)上

ImmutableState

Dojo Store 通過 Immutable 為 MutableState 接口提供了一個(gè)實(shí)現(xiàn)。如果對(duì) store 的狀態(tài)做頻繁的、較深層級(jí)的更新,則這個(gè)實(shí)現(xiàn)可能會(huì)提高性能。在最終決定使用這個(gè)實(shí)現(xiàn)之前,應(yīng)先測試和驗(yàn)證性能。

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 });

本地存儲(chǔ)

Dojo Store 提供了一組工具來使用本地存儲(chǔ)(local storage)。

本地存儲(chǔ)中間件監(jiān)視指定路徑上的變化,并使用 collector 中提供的 id 和 path 中定義的結(jié)構(gòu),將它們存儲(chǔ)在本地磁盤上。

使用本地存儲(chǔ)中間件:

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

來自 LocalStorage 中的 load 函數(shù)用于與 store 結(jié)合

與狀態(tài)結(jié)合:

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

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

注意,數(shù)據(jù)要能夠被序列化以便存儲(chǔ),并在每次調(diào)用 process 后都會(huì)覆蓋數(shù)據(jù)。此實(shí)現(xiàn)不適用于不能序列化的數(shù)據(jù)(如 DateArrayBuffer)。

高級(jí)的 store operation

Dojo Store 使用 operation 來更改應(yīng)用程序的底層狀態(tài)。這樣設(shè)計(jì) operation,有助于簡化對(duì) store 的常用交互,例如,operation 將自動(dòng)創(chuàng)建支持 addreplace operation 所需的底層結(jié)構(gòu)。

在未初始化的 store 中執(zhí)行一個(gè)深度 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)]);

結(jié)果為:

{
    "users": {
        "list": [
            {
                "id": "0",
                "name": "Paul"
            }
        ]
    }
}

即使?fàn)顟B(tài)尚未初始化,Dojo 也能基于提供的 path 創(chuàng)建出底層的層次結(jié)構(gòu)。這個(gè)操作是安全的,因?yàn)?TypeScript 和 Dojo 提供了類型安全。這允許用戶很自然的使用 store 所用的 State 接口,而不需要顯式關(guān)注 store 中保存的數(shù)據(jù)。

當(dāng)需要顯式使用數(shù)據(jù)時(shí),可以使用 test 操作或者通過獲取底層數(shù)據(jù)來斷言該信息,并通過編程的方式來驗(yàn)證。

本示例使用 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 總是作為最后一個(gè)元素添加到列表的末尾:

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)
]);

禁止訪問狀態(tài)的根節(jié)點(diǎn),如果訪問將會(huì)引發(fā)錯(cuò)誤,例如嘗試執(zhí)行 get(path('/'))。此限制也適用于 operation;不能創(chuàng)建一個(gè)更新狀態(tài)根節(jié)點(diǎn)的 operation。@dojo/framewok/stores 的最佳實(shí)踐是鼓勵(lì)只訪問 store 中最小的、必需的部分。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI