diff --git a/README.md b/README.md
index 09044058..3f0ca2c3 100644
--- a/README.md
+++ b/README.md
@@ -49,28 +49,15 @@ export default {
this.dataSource = await new Promise(resolve =>
setTimeout(() => {
resolve([
- {
- name: 'react'
- },
- {
- name: 'vue',
- done: true
- },
- {
- name: 'angular'
- }
+ { name: 'react' },
+ { name: 'vue', done: true },
+ { name: 'angular' }
]);
}, 1000)
); },
add(todo) {
this.dataSource.push(todo);
},
- remove(index) {
- this.dataSource.splice(index, 1);
- },
- toggle(index) {
- this.dataSource[index].done = !this.dataSource[index].done;
- },
};
```
* Initialize the store instance and register the pre-defined store config using the namespace.
@@ -161,6 +148,47 @@ ReactDOM.render(, rootElement);
Complete example is presented in this [sandbox](https://codesandbox.io/s/icestore-hs9fe), feel free to play with it.
+## Best Practices
+
+### Never mutate state outside actions
+
+`icestore` enforces all the mutations to the state to occur only in action methods. Mutation occurred outside actions will not take effect (e.g. in the view component).
+
+The reason is that the mutation logic would be hard to trace and impossible to test as there might be unpredictable changes made to the view components as a result of mutations outside actions.
+
+```javascript
+ // store.js
+ export default {
+ inited: false,
+ setInited() {
+ this.inited = true;
+ }
+ }
+
+ // view.js
+ const todos = useStore('todos');
+
+ useEffect(() => {
+ // bad
+ todos.inited = true;
+
+ // good
+ todos.setInited();
+ });
+```
+
+### Divide store as small as possible
+
+By design, `icestore` will trigger the rerender of all the view components subscribed to the store (by using useStore) once the state of the store has changed.
+
+This means that putting more state in one store may cause more view components to rerender, affecting the overall performance of the application. As such, it is advised to categorize your state and put them in individual stores to improve performance.
+
+### Don't overuse `icestore`
+
+From the engineering perspective, the global store should only be used to store states that are shared across multiple pages or components.
+
+Putting local state in the global store will break the component's encapsulation, affecting its reusability. Using the todo app as an example, if the app only has one page, the state of the todo app is preferred to be stored as a local state in the view component rather than in the global store.
+
## API
### registerStore
@@ -268,6 +296,140 @@ return (
);
```
+### Middleware
+
+#### Context
+
+If you have used server side frameworks such as Express or koa, you were probably familiar with the concept of middleware already. Among these frameworks, middleware is used to insert custom code between `receiving request` and `generating response`, the functionality of middlewares include data mutation、authority check before the request was handled and add HTTP header、log printing after the request was handled.
+
+In state management area, Redux also implements middleware mechanism, it was used to put custom code between `action dispatching` and `reaching reducer`. Its functionalities include log printing, async mechanism such as thunk, promise.
+
+Like Redux, the purpose of `icestore` implementing middleware mechanism is to add an extensive mechanism between action was not dispatched and dispatched. The difference is that `icestore` already supports async action, so there is no need to write middleware for async support.
+
+### middleware API
+
+`icestore` takes insiprations from koa for its middleware API design as follows:
+
+```javascript
+async (ctx, next) => {
+ // logic before action was dispatched
+
+ const result = await next();
+
+ // logic after action was dispatched
+
+ return result;
+}
+```
+Note: If there is return value in action, all the middlewares in the chain must return the executing result of the next middleware to ensure the action's return value is correctly return from middleware chain.
+
+#### ctx API
+
+* ctx.action - the object of the action dispatched
+ * Type:{object}
+ * Return Value:void
+* ctx.action.name - the name of action dispatched
+ * Type:{string}
+ * Return Value:void
+* ctx.action.arguments - the arguments of current action function
+ * Type:{array}
+ * Return Value:void
+* ctx.store - the store object
+ * Type:{object}
+ * Return Value:void
+* ctx.store.namespace - the store namespace
+ * Type:{string}
+ * Return Value:void
+* ctx.store.getState - the method to get the latest state value of current store
+ * Type:{function}
+ * Return Value:void
+
+The example is as follows:
+
+```javascript
+const {
+ action, // the object of the action dispatched
+ store, // the store object
+} = ctx;
+
+const {
+ name, // the name of action dispatched
+ arguments, // the arguments of current action function
+} = action;
+
+const {
+ namespace, // the store namespace
+ getState, // the method to get the latest state value of current store
+} = store;
+```
+
+### Registration
+
+Due the multiple store design of `icestore`, it supports registering middlewares for indivisual store as follows:
+
+1. Global registration
+ * Global registration middlewares apply to all stores.
+
+ ```javascript
+ import Icestore from '@ice/store';
+ const stores = new Icestore();
+ stores.applyMiddleware([a, b]);
+ ```
+
+2. Registration by namespace
+ * The ultimate middleware queue of single store will merge global middlewares with its own middlewares.
+
+ ```javascript
+ stores.applyMiddleware([a, b]);
+ stores.applyMiddleware([c, d], 'foo'); // store foo middleware is [a, b, c, d]
+ stores.applyMiddleware([d, c], 'bar'); // store bar middleware is [a, b, d, c]
+ ```
+
+## Debug
+
+`icestore` provide an official logger middleware to facilitate user traceing state changes and improve debug productivity.
+
+### Installation
+
+```bash
+npm install @ice/store-logger --save
+```
+
+### Guide
+
+Use `applyMiddleware` API to push logger middleware into middleware queue.
+
+```javascript
+import todos from './todos';
+import Icestore from '@ice/store';
+import logger from '@ice/store-logger';
+
+const icestore = new Icestore();
+
+const middlewares = [];
+
+// Turn off logger middleware in production enviroment
+if (process.env.NODE_ENV !== 'production') {
+ middlewares.push(logger);
+}
+
+icestore.applyMiddleware(middlewares);
+icestore.registerStore('todos', todos);
+```
+
+When action was dispatched, the log will be printed into browser's DevTools by realtime:
+
+
+
+The logger includes the following infos:
+
+* Store Name: namespace of current store
+* Action Name: action being dispatched
+* Added / Deleted / Updated: type of state changes
+* Old state: state before change
+* New state: state after change
+
+
## Testing
Because all the states and actions are contained in a plain JavaScript object, it is easy to write tests without using mock objects.
@@ -296,52 +458,6 @@ describe('todos', () => {
Please refer to the `todos.spec.js` file in the sandbox above for complete reference.
-## Best Practices
-
-### Never mutate state outside actions
-
-`icestore` enforces all the mutations to the state to occur only in action methods. An error would be thrown if the state is mutated outside actions (e.g. in the view component).
-
-The reason is that the mutation logic would be hard to trace and impossible to test as there might be unpredictable changes made to the view components as a result of mutations outside actions.
-
-```javascript
- // store.js
- export default {
- inited: false,
- setInited() {
- this.inited = true;
- }
- }
-
- // view.js
- const todos = useStore('todos');
-
- useEffect(() => {
- // bad
- todos.inited = true;
-
- // good
- todos.setInited();
- });
-```
-
-### Divide store as small as possible
-
-By design, `icestore` will trigger the rerender of all the view components subscribed to the store (by using useStore) once the state of the store has changed.
-
-This means that putting more state in one store may cause more view components to rerender, affecting the overall performance of the application. As such, it is advised to categorize your state and put them in individual stores to improve performance.
-
-### Don't overuse `icestore`
-
-From the engineering perspective, the global store should only be used to store states that are shared across multiple pages or components.
-
-Putting local state in the global store will break the component's encapsulation, affecting its reusability. Using the todo app as an example, if the app only has one page, the state of the todo app is preferred to be stored as a local state in the view component rather than in the global store.
-
-## Todos
-
-- [ ] Add debug util
-- [ ] Add middleware support
-
## Reference
- [react-hooks-model](https://github.com/yisbug/react-hooks-model)
diff --git a/README.zh-CN.md b/README.zh-CN.md
index fee92a8a..e3dff25d 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -306,7 +306,7 @@ return (
### 中间件
-### 背景
+#### 背景
如果你有使用过服务端的框架如 Express 或者 koa,应该已经熟悉了中间件的概念,在这些框架中,中间件用于在框架 `接收请求` 与 `产生响应` 间插入自定义代码,这类中间件的功能包含在请求未被响应之前对数据进行加工、鉴权,以及在请求被响应之后添加响应头、打印 log 等功能。