天天看點

Dojo Store 簡介介紹基本用法

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

介紹

Dojo store 提供可預測的、一緻的狀态容器,内置了對共享狀态管理模式的支援。

Dojo store 包提供了一個集中式存儲,為應用程式提供真正的單一資料源。Dojo 應用程式的操作使用單向資料流;是以,所有應用程式資料遵循相同的生命周期,確定應用程式邏輯是可預測的,且易于了解。

功能 描述
全局資料存儲 應用程式狀态全局存儲在真正的單一資料源中。
單向資料流 可預測的、全局的應用程式狀态管理。
類型安全 對狀态的通路和修改都受接口的保護。
操作驅動的狀态變更 封裝的、定義良好的狀态修改,可以記錄、撤消和重放。
異步支援 開箱即用的異步指令(command)支援。
操作中間件 在操作前和操作後進行錯誤處理和資料轉換。
簡單的部件內建 提供與 Dojo 部件輕松內建的工具和模式。

基本用法

Dojo 提供了一種響應式架構,能夠持續修改和渲染應用程式的目前狀态。在簡單系統中,這通常發生在部件内部,并且部件可以修改自己的狀态。然而,随着系統變得越來越複雜,就需要更好的劃分和封裝資料,并随着快速增長建立一個清晰的隔離。

Store 提供了一個清晰的接口,通過單向資料流對全局對象進行存儲、修改和檢索。Store 中包含對共享模式的支援,如異步資料擷取、中間件和撤銷。Store 及其模式允許部件聚焦于它們的主要角色,即對資訊的可視化展示和監聽使用者互動。

store 對象

store 對象存儲整個應用程式全局的、原子的狀态。應該在建立應用程式時建立 store 對象,并使用一個注入器将其定義到

Registry

中。

main.ts
import { registerStoreInjector } from '@dojo/framework/stores/StoreInjector';
import Store from '@dojo/framework/stores/Store';
import { State } from './interfaces';

const store = new Store<State>();
const registry = registerStoreInjector(store);           

State

使用接口定義全局存儲的結構。

State

中的所有内容都應是可序列化的,即能轉換為 JSON 或從 JSON 轉換回來,這樣的話, Dojo 的虛拟 DOM 系統更容易确定何時更改了資料,進而提高性能。

interfaces.d.ts
interface User {
    id: string;
    name: string;
}

export interface State {
    auth: {
        token: string;
    };
    users: {
        current: User;
        list: User[];
    };
}           

上面是一個簡單的示例,定義了 store 的結構,會在本指南的其餘示例中使用。

更新 store

使用 Dojo store 時需注意三個核心概念。

  • Operation - 操作 store 所持狀态的指令
  • Command - 執行業務邏輯并傳回 operation 的簡單函數
  • Process - 執行一組 command 和表示應用程式的行為

Command 和 operation

要修改 store 中的值,則在執行 process 時,會調用一個 command 函數。command 函數傳回要應用到 store 上的一系列 operation。每個 command 都要傳入一個

CommandRequest

參數,它提供了

path

at

函數,會以類型安全的方式生成

Path

,也提供了

get

函數來通路 store 中的狀态,以及提供

payload

對象來為被調用的 process 執行器傳入參數。

Command 工廠

Store 中有一個簡單的封裝函數,用于建立 command,是一個類型安全的工廠函數。

建立 store 工廠:

import { createCommandFactory } from '@dojo/framework/stores/process';
import { State } from './interfaces';

const createCommand = createCommandFactory<State>();

const myCommand = createCommand(({ at, get, path, payload, state }) => {
    return [];
});           

createCommand

確定封裝的 command 具有正确的類型,而傳入的

CommandRequest

函數能獲得通過

createCommandFactory

提供的

State

接口的類型。雖然可以手動為 command 設定類型,但本指南中的示例使用

createCommand

path

path 是一個

string

,用于描述應用 operation 的位置。

path

函數是

CommandRequest

中的一部分,可以在

Command

中通路。

本示例中,

path

描述了 store 中的一個位置。

State

與上面

interface.d.ts

中定義的相同。

Store

通過

State

接口獲知狀态資料的形狀。

定義一個擷取目前使用者名的

path

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

path('users', 'current', 'name');           

這個 path 引用的

string

值位于

/users/current/name

path

以類型安全的方式周遊層次結構,確定隻能使用在

State

接口中定義的屬性名。

at

at

函數與

path

函數一起辨別數組中的位置。本示例使用了

at

函數。

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

at(path('users', 'list'), 1);           

這個 path 引用的是位于

/user/list

中偏移量為

1

User

add

operation

用于向對象中添加值或者向數組中插入值。

import { createCommandFactory } from '@dojo/framework/stores/process';
import { State } from './interfaces';
import { add } from '@dojo/framework/stores/state/operations';

const createCommand = createCommandFactory<State>();
const myCommand = createCommand(({ at, get, path, payload, state }) => {
    const user = { id: '0', name: 'Paul' };

    return [add(at(path('users', 'list'), 0), user)];
});           

會将

user

插入到使用者清單的起始位置。

remove

從對象或數組中移除值。

import { createCommandFactory } from '@dojo/framework/stores/process';
import { State } from './interfaces';
import { add, remove } from '@dojo/framework/stores/state/operations';

const createCommand = createCommandFactory<State>();
const myCommand = createCommand(({ at, get, path, payload, state }) => {
    const user = { id: '0', name: 'Paul' };

    return [
        add(path('users'), {
            current: user,
            list: [user]
        }),
        remove(at(path('users', 'list'), 0))
    ];
});           

本示例先為

users

添加一個初始狀态,然後移除 list 中的第一個

user

replace

替換值。相當于先

remove

add

import { createCommandFactory } from '@dojo/framework/stores/process';
import { State } from './interfaces';
import { add, replace } from '@dojo/framework/stores/state/operations';

const createCommand = createCommandFactory<State>();
const myCommand = createCommand(({ at, get, path, payload, state }) => {
    const users = [{ id: '0', name: 'Paul' }, { id: '1', name: 'Michael' }];
    const newUser = { id: '2', name: 'Shannon' };

    return [
        add(path('users'), {
            current: user[0],
            list: users
        }),
        replace(at(path('users', 'list'), 1), newUser)
    ];
});           

本示例使用

newUser

替換掉

list

中的第二個使用者資訊。

get

get

函數會傳回 store 在指定 path 位置的值,如果該位置不存在值,則傳回

undefined

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 updateCurrentUser = createCommand(async ({ at, get, path }) => {
    const token = get(path('auth', 'token'));

    if (!token) {
        return [remove(path('users', 'current'))];
    } else {
        const user = await fetchCurrentUser(token);
        return [replace(path('users', 'current'), user)];
    }
});           

本示例檢查是否存在身份認證令牌,然後據此更新目前使用者的資訊。

payload

payload

是一個對象字面量,當 process 調用 command 時,會将其傳給 command。也可以在建構指令時傳入

payload

的類型。

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>(({ at, path, payload }) => {
    return [add(at(path('users', 'list'), 0), payload)];
});           

本示例将

payload

提供的使用者資訊添加到

/users/list

的起始位置。

異步 command

command 可以同步執行,也可以異步執行。異步 command 應該傳回一個

Promise

,以便指出何時完成。每個 command 成功完成後,将自動收集和應用 operation。

Process

Process

store

上按順序執行 command,以修改應用程式的狀态。使用

createProcess

工廠函數建立 process,該函數可傳入一系列 command,以及選擇性的傳入一系列中間件。

建立 process

首先,建立兩個 command,負責擷取使用者令牌,并使用該令牌加載

User

。然後建立一個 process 來使用這兩個 command。每一個 process 都應該使用 ID 唯一辨別。此 ID 在 store 内部使用。

import { createCommandFactory, createProcess } from "@dojo/framework/stores/process";
import { State } from './interfaces';
import { add, replace } from "@dojo/framework/stores/state/operations";

const createCommand = createCommandFactory<State>();

const fetchUser = createCommand(async ({ at, get, payload: { username, password } }) => {
    const token = await fetchToken(username, password);

    return [
        add(path('auth', 'token'), token);
    ];
}

const loadUserData = createCommand(async ({ path }) => {
    const token = get(path('auth', 'token'));
    const user = await fetchCurrentUser(token);
    return [
        replace(path('users', 'current'), user)
    ];
});

export const login = createProcess('login', [ fetchUser, loadUserData ]);           

payload

類型

process 執行器(process executor)的

payload

是從 command 的

payload

類型推斷出來的。如果指令間的 payload 類型不同,則需要顯式定義 process 執行器的

payload

類型。

const createCommand = createCommandFactory<State>();

const commandOne = createCommand<{ one: string }>(({ payload }) => {
    return [];
});

const commandTwo = createCommand<{ two: string }>(({ payload }) => {
    return [];
});

const process = createProcess<State, { one: string; two: string }>('example', [commandOne, commandTwo]);

process(store)({ one: 'one', two: 'two' });           

關聯部件和 store

有兩個狀态容器可用于部件:

StoreContainer

StoreProvider

。這些容器将應用程式的 store 與部件關聯起來。當使用函數部件時,也可以建立類型化的 store 中間件。

注意,本節旨在介紹部件和狀态(通過 store 提供的)是如何關聯起來的。有關部件狀态管理的更多資訊,請參閱建立部件參考指南。

Store 中間件

當使用基于函數的部件時,

createStoreModdleware

幫助函數用于建立類型化的 store 中間件,讓部件能通路 store。

middleware/store.ts
import createStoreMiddleware from '@dojo/framework/core/middleware/store';
import { State } from '../interfaces';

export default createStoreMiddleware<State>();           
widgets/User.tsx
import { create } from '@dojo/framework/core/vdom';
import store from '../middleware/store';
import { State } from '../../interfaces';

const factory = create({ store }).properties();
export const User = factory(function User({ middleware: { store } }) {
    const { get, path } = store;
    const name = get(path('users', 'current', 'name'));

    return {`Hello, ${name}`};
});           

此中間件包含一個

executor

方法,用于在 store 上運作 process。

import { create } from '@dojo/framework/core/vdom';
import store from '../middleware/store';
import logout from '../processes/logout';
import { State } from '../../interfaces';

const factory = create({ store }).properties();
export const User = factory(function User({ middleware: { store } }) {
    const { get, path } = store;
    const name = get(path('users', 'current', 'name'));

    const onLogOut = () => {
        store.executor(logout)({});
    };

    return (

            {`Hello, ${name}`}
            <button onclick={onLogOut}>Log Out</button>

    );
});           

StoreProvider

StoreProvider

是一個 Dojo 部件,它擁有

renderer

,并與 store 關聯。它總是封裝在另一個部件内,因為它無法定義自己的屬性。

widget/User.ts
import { create } from '@dojo/framework/core/vdom';
import { State } from '../../interfaces';

const factory = create().properties();
export const User = factory(function User() {
    return (
        <StoreProvider
            stateKey="state"
            paths={(path) => [path('users', 'current')]}
            renderer={(store) => {
                const { get, path } = store;
                const name = get(path('users', 'current', 'name'));

                return {`Hello, ${name}`};
            }}
        />
    );
});           

StoreProvider

User

渲染内容的一部分,并且跟其它 Dojo 部件一樣,提供了自己的

renderer

Container

Container

是一個部件,它完全封裝另一個部件。它使用

getProperties

函數将 store 關聯到部件上。

widget/User.tsx
import { create, tsx } from '@dojo/framework/core/vdom';

interface UserProperties {
    name?: string;
}
const factory = create().properties<UserProperties>();
export const User = factory(function User({ properties }) {
    const { name = 'Stranger' } = properties();
    return {`Hello, ${name}`};
});           
widget/User.container.ts
import { createStoreContainer } from '@dojo/framework/stores/StoreContainer';
import { State } from '../interfaces';
import User from './User';

const StoreContainer = createStoreContainer<State>();

const UserContainer = StoreContainer(User, 'state', {
    getProperties({ get, path }) {
        const name = get(path('user', 'current', 'name'));

        return { name };
    }
});           

執行 process

import { logout } from './processes/logout';
import StoreProvider from '@dojo/framework/stores/StoreProvider';
import { State } from '../../interfaces';
import User from './User';
import { create, tsx } from '@dojo/framework/core/vdom';

const factory = create().properties();
export const UserProvider = factory(function UserProvider() {
    return (
        <StoreProvider
            stateKey="state"
            paths={(path) => [path('users', 'current')]}
            renderer={(store) => {
                const { get, path } = store;
                const name = get(path('users', 'current', 'name'));
                const onLogOut = () => {
                    logout(store)({});
                };

                return <User name={name} onLogOut={onLogOut} />;
            }}
        />
    );
});           

繼續閱讀