您好,登錄后才能下訂單哦!
翻譯自 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 對象,并使用一個注入器將其定義到 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 的結構,會在本指南的其余示例中使用。
使用 Dojo store 時需注意三個核心概念。
要修改 store 中的值,則在執行 process 時,會調用一個 command 函數。command 函數返回要應用到 store 上的一系列 operation。每個 command 都要傳入一個 CommandRequest
參數,它提供了 path
和 at
函數,會以類型安全的方式生成 Path
,也提供了 get
函數來訪問 store 中的狀態,以及提供 payload
對象來為被調用的 process 執行器傳入參數。
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
operation從對象或數組中移除值。
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
operation替換值。相當于先 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 應該返回一個 Promise
,以便指出何時完成。每個 command 成功完成后,將自動收集和應用 operation。
Process
在 store
上按順序執行 command,以修改應用程序的狀態。使用 createProcess
工廠函數創建 process,該函數可傳入一系列 command,以及選擇性的傳入一系列中間件。
首先,創建兩個 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' });
有兩個狀態容器可用于部件:StoreContainer
和 StoreProvider
。這些容器將應用程序的 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 <h2>{`Hello, ${name}`}</h2>;
});
此中間件包含一個 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 (
<h2>
{`Hello, ${name}`}
<button onclick={onLogOut}>Log Out</button>
</h2>
);
});
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 <h2>{`Hello, ${name}`}</h2>;
}}
/>
);
});
StoreProvider
是 User
渲染內容的一部分,并且跟其它 Dojo 部件一樣,提供了自己的 renderer
。
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 <h2>{`Hello, ${name}`}</h2>;
});
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 };
}
});
本示例中,UserContainer
封裝了顯示當前用戶名的 User
。createStoreContainer
是一個封裝器,用于確保 getProperties
中的類型正確。getProperties
負責從 store 中訪問數據,并為部件創建屬性。
StoreContainer
屬性與其封裝部件的屬性是 1:1 映射的。部件的屬性成為 StoreContainer
的屬性,但這些屬性都是可選的。
process 只是為一組數據定義了一個執行流。要執行 process,就需要讓 process 基于 store 創建一個執行器。StoreContainer
和 StoreProvider
部件都支持訪問 store。
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} />;
}}
/>
);
});
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。