Translated from: https://github.com/dojo/framework/blob/master/docs/en/stores/supplemental.md
State
Objects
In modern browsers, state
the object as CommandRequest
part of the incoming. For state
any changes to the object are converted to the corresponding operation, and then applied to the 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];
});
Note, IE 11 does not support access to state, if you try to access will immediately throw an error.
StoreProvider
Receiving three properties StoreProvider
renderer
: A rendering function, which has been injected store, access to sub-state member incoming process.stateKey
: Key used for registration status value.paths
(Optional): This state is connected to the provider a local.
Fail
StoreProvider
There are two ways to trigger the failure and to promote the re-rendering.
- The recommended way is by passing the provider
paths
to register propertypath
, to ensure that the state will fail when the only relevant changes. - Another is a more general way, when there is no provider is defined as
path
when, in Store any data changes will cause failure.
Process
The life cycle
Process
The implementation of a life cycle, which defines the behavior defined processes.
- If the converter is present, it is executed first converter to convert the payload objects
- In order to perform synchronization
before
middleware - Execution command sequence defined
- After performing each command (if more than one command is command), an application command operation is returned
- If an exception is thrown during the execution of the command, the command will not perform follow-up, and it will not apply the current operation
- In order to perform synchronization
after
middleware
Process Middleware
With optional before
and after
methods before and after the process of the middleware. This allows the front and rear behavior defined in general process is added, the operation can be shared.
You can also define multiple middleware in the list. It will be based on a synchronous call middleware order in the list.
Before
before
Middleware can get block incoming payload
and store
references.
middleware/beforeLogger.ts
const beforeOnly: ProcessCallback = () => ({
before(payload, store) {
console.log('before only called');
}
});
After
after
Middleware block can get passed error
(if an error occurs) and the process result
.
middleware/afterLogger.ts
const afterOnly: ProcessCallback = () => ({
after(error, result) {
console.log('after only called');
}
});
result
It implements Proce***esult
interface to provide information about the application to change the store and provide access to the store.
executor
- allow the process to run on other storestore
- store referencesoperations
- operation of a set of applicationsundoOperations
- a set of operation, operation applied to revokeapply
- apply methods on storepayload
- payload providedid
- id of the process for naming
Subscribe to changes in store
Store
There is a onChange(path, callback)
method that a receiver or a set path, and calls the callback function when the state change.
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
There is also an invalidate
event, the event triggered when the store changes.
main.ts
store.on('invalidate', () => {
// do something when the store's state has been updated.
});
Shared state management
The initial state
When you first create a store, it is empty. Then, a process may be used for the initial filling of the store application state.
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
Store using the Dojo patch operation to track changes in the underlying store. In this way, Dojo it is easy to create a set of operation, and then revoked this group operation, in order to recover any data set of command modified. undoOperations
Is Proce***esult
part of, may be after
used in the middleware.
When a process comprising a plurality of modified state store command, and wherein a command fails, rollback, the revocation (Undo) operation is useful.
undo middleware
const undoOnFailure = () => {
return {
after: () => (error, result) {
if (error) {
result.store.apply(result.undoOperations);
}
}
};
};
const process = createProcess('do-something', [
command1, command2, command3
], [ undoOnFailure ])
When executed, any command error, the undoOnFailure
middleware is responsible for the application undoOperations
.
It should be noted that undoOperations
only applies to command the process is completely executed. When rollback state, it will not contain any of the following Operation, change these states may be caused by another process executed asynchronously, or in the state of performing the change in a middleware, or directly on a store operation. These systems cases not undo range.
Optimistic update
Optimistic update can be used to build a responsive UI, although the interaction may take some time to respond, for example, to save a remote resource.
For example, prior to being added if a todo item, may send a request by a persistent object server to update optimism, todo items will be added to the store in order to avoid the embarrassment of waiting or loading indicators. When the server response, success, to coordinate todo item store server according to the result of the operation.
In a successful scenario, use server response provided id
to update the added Todo
items, and Todo
color item to green to indicate saved successfully.
In the error scenario, a notification may be displayed, indicating that the request failed, and Todo
the color of the item to red while displaying a "Retry" button. Even restore or revoke added Todo items, as well as any other operations that occur in the 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
- Add a todo item in the application statecalculateCountsCommand
The number of to-do item to-do recalculate the number of completed items and activities -postTodoCommand
- will be presented to the remote service todo items, and use the process ofafter
the middleware perform further changes when the error occurred- Failure to recover the changes, and failed status field is set to true
- When successfully use the updated value of the id field todo items returned from the remote service
calculateCountsCommand
-postTodoCommand
after the successful run once
Synchronization Update
In some cases, before proceeding with the process, the best back-end call is completed. For example, when the process remove an element from the screen, or outlet changed to display different views, triggers the state to restore these operations may make people feel very strange (Annotation: start with the interface data deleted, because deleting the background failure, after a while the data appeared on the screen).
Because the process supports asynchronous command, simply return Promise
to wait for the results.
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]);
Concurrent command
Process
Supports concurrent execution of multiple command, simply put one of these command to the array.
process.ts
createProcess('my-process', [commandLeft, [concurrentCommandOne, concurrentCommandTwo], commandRight]);
In the present example, commandLeft
priority of execution concurrent execution concurrentCommandOne
and concurrentCommandTwo
. When all concurrent command execution is complete, on-demand application of the results returned. If any concurrent command error will not apply any operation. Finally, the implementation commandRight
.
Alternatively the state achieved
When instantiated store, use the default MutableState
implementation of the interface. In most cases, the default state of the interface have been optimized well enough for common situations. If a particular embodiment requires use another implementation, the incoming can be achieved during initialization.
const store = new Store({ state: myStateImpl });
MutableState
API
Any State
implementation must provide four ways to correct on the state of the application operation.
get<S>(path: Path<M, S>): S
A receivingPath
object and returns the value in the current state of the path pointsat<S extends Path<M, Array<any>>>(path: S, index: number): Path<M, S['value'][0]>
It returns anPath
object that is positioned to point to the path array indexindex
valuepath: StatePaths<M>
Type-safe way, the state generates a given pathPath
objectsapply(operations: PatchOperation<T>[]): PatchOperation<T>[]
operation of applications will be provided to the current state
ImmutableState
Dojo Store by Immutable provides an implementation for the MutableState interface. If you make frequent state store, and update deeper level, it is this implementation may improve performance. Before the final decision to use this implementation, you should test and verify performance.
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 });
Local storage
Dojo Store provides a set of tools to use local storage (local storage).
Monitoring changes in the local storage middleware specified path, and using the collector
provided id
and defined in the configuration path, they will be stored on the local disk.
Using local storage middleware:
export const myProcess = createProcess(
'my-process',
[command],
collector('my-process', (path) => {
return [path('state', 'to', 'save'), path('other', 'state', 'to', 'save')];
})
);
From LocalStorage
the load
function for binding to store
Combined with the state:
import { load } from '@dojo/framework/stores/middleware/localStorage';
import { Store } from '@dojo/framework/stores/Store';
const store = new Store();
load('my-process', store);
Note that the data to be serialized for storage, and the data are overwritten after each call process. This does not apply to not implement the serialized data (such as Date
and ArrayBuffer
).
Advanced store operation
Dojo Store using the operation to change the state of the underlying application. Designed operation, will help simplify common interaction of the store, for example, operation will automatically create a support add
or replace
infrastructure required for the operation.
Performing a depth in the uninitialized 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)]);
The results are:
{
"users": {
"list": [
{
"id": "0",
"name": "Paul"
}
]
}
}
Even if the state has not been initialized, Dojo can create a hierarchy based on the underlying path provided. This operation is safe because TypeScript and Dojo provides type safety. This allows the user to use a natural store used by State
the interface, without the need for explicit saving of interest data store.
When the data requires explicit use, you can test
operate to assert or by obtaining information from the underlying data, and programmatically verified.
This example uses test
operations to ensure initialized to ensure always user
added to the end of the list:
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))]);
This example programmatically, make sure that user
is always added as the last element to the end of the list:
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)
]);
Prohibit access to state of the root node, if the visit will lead to error, for example, try to execute get(path('/'))
. This restriction also applies to operation; you can not create a status update root operation. @dojo/framewok/stores
The best practice is to encourage access only store in the minimum necessary parts.