Skip to content

Commit

Permalink
feat: add en docs (#33)
Browse files Browse the repository at this point in the history
* feat: support middleware && add debug middleware

* feat: add README of icestore-debug

* feat: delete next in last middleware

* fix: lint

* fix: test

* feat: improve test

* fix: lint

* feat: middleware API simplify

* feat: update API according to PR review

* fix:  lint and test

* feat: delete Proxy

* fix: lint

* feat: remove state diff

* feat: update API according to latest PR review

* chore: remove unused dependency

* test: add action value

* feat: split util functions to individual files

* feat: support action return value from middleware

* chore: remove unused util

* fix: util path

* feat: split util functions to individual files

* feat: update README

* feat: remove toJS in icestore-logger

* feat: move middleware logic to store

* fix: loading and error status

* fix: PR review

* feat: add test

* fix: not throw error

* feat: add test

* feat: update README

* feat: README fix

* feat: add en docs
  • Loading branch information
temper357 authored and alvinhui committed Aug 22, 2019
1 parent 52e39cd commit e879298
Show file tree
Hide file tree
Showing 2 changed files with 179 additions and 63 deletions.
240 changes: 178 additions & 62 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -161,6 +148,47 @@ ReactDOM.render(<Todo />, 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
Expand Down Expand Up @@ -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:

<img src="https://user-images.githubusercontent.com/5419233/63344463-13184300-c383-11e9-96da-2de3b41f6e9b.png" width="250" />

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.
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ return (

### 中间件

### 背景
#### 背景

如果你有使用过服务端的框架如 Express 或者 koa,应该已经熟悉了中间件的概念,在这些框架中,中间件用于在框架 `接收请求``产生响应` 间插入自定义代码,这类中间件的功能包含在请求未被响应之前对数据进行加工、鉴权,以及在请求被响应之后添加响应头、打印 log 等功能。

Expand Down

0 comments on commit e879298

Please sign in to comment.