diff --git a/README.md b/README.md
index 4d9564e9..270f5011 100644
--- a/README.md
+++ b/README.md
@@ -1,350 +1,110 @@
-English | [简体中文](./README.zh-CN.md)
-
# icestore
-> Lightweight React state management library based on react hooks
+> Lightweight state management solution based on React Hooks.
[![NPM version](https://img.shields.io/npm/v/@ice/store.svg?style=flat)](https://npmjs.org/package/@ice/store)
[![Package Quality](https://npm.packagequality.com/shield/@ice%2Fstore.svg)](https://packagequality.com/#?package=@ice/store)
[![build status](https://img.shields.io/travis/ice-lab/icestore.svg?style=flat-square)](https://travis-ci.org/ice-lab/icestore)
-[![Test coverage](https://img.shields.io/codecov/c/github/ice-lab/icestore.svg?style=flat-square)](https://codecov.io/gh/ice-lab/icestore)
[![NPM downloads](http://img.shields.io/npm/dm/@ice/store.svg?style=flat)](https://npmjs.org/package/@ice/store)
[![Known Vulnerabilities](https://snyk.io/test/npm/@ice/store/badge.svg)](https://snyk.io/test/npm/@ice/store)
[![David deps](https://img.shields.io/david/ice-lab/icestore.svg?style=flat-square)](https://david-dm.org/ice-lab/icestore)
-## Installation
-
-icestore requires React 16.8.0 or later.
-
-```bash
-npm install @ice/store --save
-```
+
## Introduction
icestore is a lightweight React state management library based on hooks. It has the following core features:
-* **Minimal & Familiar API**: No additional learning costs, easy to get started with the knowledge of React Hooks.
-* **Class Component Support**: Make old projects enjoying the fun of lightweight state management with friendly compatibility strategy.
+* **Minimal & Familiar API**: No additional learning costs, easy to get started with the knowledge of React Hooks, friendly to Redux users.
* **Built in Async Status**: Records loading and error status of async actions, simplifying the rendering logic in the view layer.
+* **Class Component Support**: Make old projects enjoying the fun of lightweight state management with friendly compatibility strategy.
* **TypeScript Support**: Provide complete type definitions to support intelliSense in VS Code.
-## Getting Started
-
-Let's build a simple todo app from scatch using icestore which includes following steps:
+See the [comparison table](docs/recipes.md#Comparison) for more details.
-### Step 1 - Use a model to define your store:
-
-```javascript
-export const todos = {
- state: {
- dataSource: [],
- },
- actions: {
- async fetch(prevState, actions) {
- await delay(1000);
- const dataSource = [
- { name: 'react' },
- { name: 'vue', done: true},
- { name: 'angular' },
- ];
- return {
- ...prevState,
- dataSource,
- }
- },
- add(prevState, todo) {
- return {
- ...prevState,
- dataSource: [
- ...prevState.dataSource,
- todo,
- ]
- };
- },
- },
-};
-```
-
-### Step 2 - Create the store
-
-```javascript
-import { createStore } from '@ice/store';
-import * as models from './models';
-
-export default createStore(models);
-```
-
-### Step 3 - Wrap your application
+## Basic example
```jsx
import React from 'react';
import ReactDOM from 'react-dom';
-import store from './store';
-
-const { Provider } = store;
-
-ReactDOM.render(
-
-
- ,
- rootEl
-);
-```
-
-### Step 4 - Consume model
-
-```jsx
-import React, { useEffect } from 'react';
-import store from './store';
-
-const { useModel } = store;
-
-function Todos() {
- const [ state, actions ] = useModel('todos');
- const { dataSource } = state;
- const { fetch, add } = actions;
-
- useEffect(() => {
- fetch();
- }, []);
-
- function onAdd(event, name) {
- if (event.keyCode === 13) {
- add({ name: event.target.value });
- event.target.value = '';
- }
- }
-
- return (
-
-
- {dataSource.map(({ name, done }, index) => (
-
-
- {done ? {name} : {name} }
-
-
- ))}
-
-
-
-
-
- );
-}
-```
-
-## API
-
-**createStore**
-
-`createStore(models)`
-
-The function called to create a store.
-
-```js
import { createStore } from '@ice/store';
-const store = createStore(models);
-const { Provider, useModel, withModel } = store;
-```
-
-### Parameters
-
-**models**
-
-```js
-import { createStore } from '@ice/store'
-
-const counterModel = {
- state: {
- value: 0
- },
-};
-
-const models = {
- counter: counterModel
-};
-
-createStore(models)
-```
-
-#### state
-
-`state: any`: Required
-
-The initial state of the model.
-
-```js
-const model = {
- state: { loading: false },
-};
-```
-
-#### actions
-
-`actions: { [string]: (prevState, payload, actions, globalActions) => any }`
+const delay = (time) => new Promise((resolve) => setTimeout(() => resolve(), time));
-An object of functions that change the model's state. These functions take the model's previous state and a payload, and return the model's next state.
-
-```js
+// 1️⃣ Use a model to define your store
const counter = {
state: 0,
actions: {
- add: (prevState, payload) => prevState + payload,
- }
-};
-```
-
-Actions provide a simple way of handling async actions when used with async/await:
-
-```js
-const counter = {
- actions: {
- async addAsync(prevState, payload) => {
+ increment:(prevState) => prevState + 1,
+ async decrement(prevState) {
await delay(1000);
- return prevState + payload;
- },
- }
-};
-```
-
-You can call another action by useing `actions` or `globalActions`:
-
-```js
-const user = {
- state: {
- foo: [],
- }
- actions: {
- like(prevState, payload, actions, globalActions) => {
- actions.foo(payload); // call user's actions
- globalActions.user.foo(payload); // call actions of another model
-
- // do something...
-
- return {
- ...prevState,
- };
- },
- foo(prevState, id) {
- // do something...
-
- return {
- ...prevState,
- };
+ return prevState - 1;
},
- }
-};
-```
-
-### Return
-
-#### Provider
-
-`Provider(props: { children, initialStates })`
-
-Exposes the store to your React application, so that your components will be able to consume and interact with the store via the hooks.
-
-```jsx
-import React from 'react';
-import ReactDOM from 'react-dom';
-import { createStore } from '@ice/store';
-
-const { Provider } = createStore(models);
-ReactDOM.render(
-
-
- ,
- rootEl
-);
-```
-
-#### useModel
-
-`useModel(name: string): [ state, actions ]`
-
-A hook granting your components access to the model instance.
-
-```jsx
-const counter = {
- state: {
- value: 0
},
- actions: {
- add: (prevState, payload) => ({...prevState, value: prevState.value + payload}),
- }
};
-const { userModel } = createStore({ counter });
-
-function FunctionComponent() {
- const [ state, actions ] = userModel('name');
+const models = {
+ counter,
+};
- state.value; // 0
+// 2️⃣ Create the store
+const store = createStore(models);
- actions.add(1); // state.value === 1
+// 3️⃣ Consume model
+const { useModel } = store;
+function Counter() {
+ const [ count, actions ] = useModel('counter');
+ const { increment, decrement } = actions;
+ return (
+
+ {count}
+ +
+ -
+
+ );
}
-```
-#### useModelActions
-
-`useModelActions(name: string): actions`
-
-A hook granting your components access to the model actions.
-
-```js
-function FunctionComponent() {
- const actions = useModelActions('name');
- actions.add(1);
+// 4️⃣ Wrap your components with Provider
+const { Provider } = store;
+function App() {
+ return (
+
+
+
+ );
}
-```
-
-#### useModelActionsState
-
-`useModelActionsState(name: string): { [actionName: string]: { isLoading: boolean, error: Error } } `
-A hook granting your components access to the action state of the model.
+const rootElement = document.getElementById('root');
+ReactDOM.render( , rootElement);
+```
-```js
-function FunctionComponent() {
- const actions = useModelActions('name');
- const actionsState = useModelActionsState('name');
+## Installation
- useEffect(() => {
- actions.fetch();
- }, []);
+icestore requires React 16.8.0 or later.
- actionsState.fetch.isLoading;
- actionsState.fetch.error;
-}
+```bash
+npm install @ice/store --save
```
-#### withModel
+## API
-`withModel(name: string, mapModelToProps?: (model: [state, actions]) => Object = (model) => ({ [name]: model }) ): (React.Component) => React.Component`
+[docs/api](./docs/api.md)
-Use withModel to connect the model and class component:
+## Recipes
-```jsx
-class TodoList extends Component {
- render() {
- const { counter } = this.props;
- const [ state, actions ] = counter;
- const { dataSource } = state;
-
- state.value; // 0
-
- actions.add(1);
- }
-}
-
-export default withModel('counter')(TodoList);
-```
+[docs/recipes](./docs/recipes.md)
## Browser Compatibility
@@ -362,6 +122,15 @@ Feel free to report any questions as an [issue](https://github.com/alibaba/ice/i
If you're interested in icestore, see [CONTRIBUTING.md](https://github.com/alibaba/ice/blob/master/.github/CONTRIBUTING.md) for more information to learn how to get started.
+## Community
+
+| DingTalk community | GitHub issues | Gitter |
+|-------------------------------------|--------------|---------|
+| | [issues] | [gitter]|
+
+[issues]: https://github.com/alibaba/ice/issues
+[gitter]: https://gitter.im/alibaba/ice
+
## License
[MIT](LICENSE)
diff --git a/README.zh-CN.md b/README.zh-CN.md
deleted file mode 100644
index be626a2a..00000000
--- a/README.zh-CN.md
+++ /dev/null
@@ -1,366 +0,0 @@
-[English](./README.md) | 简体中文
-
-# icestore
-
-> 基于 React Hooks 实现的轻量级状态管理框架
-
-[![NPM version](https://img.shields.io/npm/v/@ice/store.svg?style=flat)](https://npmjs.org/package/@ice/store)
-[![Package Quality](https://npm.packagequality.com/shield/@ice%2Fstore.svg)](https://packagequality.com/#?package=@ice/store)
-[![build status](https://img.shields.io/travis/ice-lab/icestore.svg?style=flat-square)](https://travis-ci.org/ice-lab/icestore)
-[![Test coverage](https://img.shields.io/codecov/c/github/ice-lab/icestore.svg?style=flat-square)](https://codecov.io/gh/ice-lab/icestore)
-[![NPM downloads](http://img.shields.io/npm/dm/@ice/store.svg?style=flat)](https://npmjs.org/package/@ice/store)
-[![Known Vulnerabilities](https://snyk.io/test/npm/@ice/store/badge.svg)](https://snyk.io/test/npm/@ice/store)
-[![David deps](https://img.shields.io/david/ice-lab/icestore.svg?style=flat-square)](https://david-dm.org/ice-lab/icestore)
-
-## 安装
-
-使用 icestore 需要 React 在 16.8.0 版本以上。
-
-```bash
-$ npm install @ice/store --save
-```
-
-## 简介
-
-icestore 是基于 React Hooks 实现的轻量级状态管理框架,具有以下特征:
-
-* **简单、熟悉的 API**:不需要额外的学习成本,只需要声明模型,然后 `useModel`;
-* **支持组件 Class 写法**:友好的兼容策略可以让老项目享受轻量状态管理的乐趣;
-* **集成异步处理**:记录异步操作时的执行状态,简化视图中对于等待或错误的处理逻辑;
-* **良好的 TypeScript 支持**:提供完整的 TypeScript 类型定义,在 VS Code 中能获得完整的类型检查和推断。
-
-## 快速开始
-
-让我们使用 icestore 开发一个简单的 Todo 应用,包含以下几个步骤:
-
-### 第一步:定义模型
-
-```javascript
-export const todos = {
- state: {
- dataSource: [],
- },
- actions: {
- async fetch(prevState, actions) {
- await delay(1000);
- const dataSource = [
- { name: 'react' },
- { name: 'vue', done: true},
- { name: 'angular' },
- ];
- return {
- ...prevState,
- dataSource,
- }
- },
- add(prevState, todo) {
- return {
- ...prevState,
- dataSource: [
- ...prevState.dataSource,
- todo,
- ]
- };
- },
- },
-};
-```
-
-### 第二步:创建 Store
-
-```javascript
-import { createStore } from '@ice/store';
-import * as models from './models';
-
-export default createStore(models);
-```
-
-### 第三步:挂载 Store
-
-```jsx
-import React from 'react';
-import ReactDOM from 'react-dom';
-import store from './store';
-
-const { Provider } = store;
-
-ReactDOM.render(
-
-
- ,
- rootEl
-);
-```
-
-### 第四步:消费模型
-
-```jsx
-import React, { useEffect } from 'react';
-import store from './store';
-
-const { useModel } = store;
-
-function Todos() {
- const [ state, actions ] = useModel('todos');
- const { dataSource } = state;
- const { fetch, add } = actions;
-
- useEffect(() => {
- fetch();
- }, []);
-
- function onAdd(event, name) {
- if (event.keyCode === 13) {
- add({ name: event.target.value });
- event.target.value = '';
- }
- }
-
- return (
-
-
- {dataSource.map(({ name, done }, index) => (
-
-
- {done ? {name} : {name} }
-
-
- ))}
-
-
-
-
-
- );
-}
-```
-
-## API
-
-**createStore**
-
-`createStore(models)`
-
-该函数用于创建 Store,将返回一个 Provider 和一些 Hooks。
-
-```js
-import { createStore } from '@ice/store';
-
-const store = createStore(models);
-const { Provider, useModel, withModel } = store;
-```
-
-### 入参
-
-**models**
-
-```js
-import { createStore } from '@ice/store'
-
-const counterModel = {
- state: {
- value: 0
- },
-};
-
-const models = {
- counter: counterModel
-};
-
-createStore(models)
-```
-
-#### state
-
-`state: any`:必填
-
-该模型的初始 state。
-
-```js
-const model = {
- state: { loading: false },
-};
-```
-
-#### actions
-
-`actions: { [string]: (prevState, payload, actions, globalActions) => any }`
-
-一个改变该模型 state 的所有函数的对象。这些函数采用模型的上一次 state 和一个 payload 作为形参,并且返回模型的下一个状态。
-
-```js
-const counter = {
- state: 0,
- actions: {
- add: (prevState, payload) => prevState + payload,
- }
-};
-```
-
-action 可以是异步的:
-
-```js
-const counter = {
- actions: {
- async addAsync(prevState, payload) => {
- await delay(1000);
- return prevState + payload;
- },
- }
-};
-```
-
-可以在返回前执行另一个 action 或者另一个模型的 actions:
-
-```js
-const user = {
- state: {
- foo: [],
- }
- actions: {
- like(prevState, payload, actions, globalActions) => {
- actions.foo(payload); // 调用本模型的 foo
- globalActions.user.foo(payload); // 调用其他模型的 foo
-
- // 做一些操作
-
- return {
- ...prevState,
- };
- },
- foo(prevState, id) {
- // 做一些操作
-
- return {
- ...prevState,
- };
- },
- }
-};
-```
-
-### 返回值
-
-#### Provider
-
-`Provider(props: { children, initialStates })`
-
-将 Store 挂载到 React 应用,以便组件能够通过 Hooks 使用 Store 并与 Store 进行交互。
-
-```jsx
-import React from 'react';
-import ReactDOM from 'react-dom';
-import { createStore } from '@ice/store';
-
-const { Provider } = createStore(models);
-ReactDOM.render(
-
-
- ,
- rootEl
-);
-```
-
-#### useModel
-
-`useModel(name: string): [ state, actions ]`
-
-在组件内使用模型实例。
-
-```jsx
-const counter = {
- state: {
- value: 0
- },
- actions: {
- add: (prevState, payload) => ({...prevState, value: prevState.value + payload}),
- }
-};
-
-const { userModel } = createStore({ counter });
-
-function FunctionComponent() {
- const [ state, actions ] = userModel('name');
-
- state.value; // 0
-
- actions.add(1); // state.value === 1
-}
-```
-
-#### useModelActions
-
-`useModelActions(name: string): actions`
-
-useModelActions 提供了一种只使用模型的 actions 但不订阅模型更新的的方式。
-
-```js
-function FunctionComponent() {
- const actions = useModelActions('name');
- actions.add(1);
-}
-```
-
-#### useModelActionsState
-
-`useModelActionsState(name: string): { [actionName: string]: { isLoading: boolean, error: Error } } `
-
-使用 useModelActionsState 来获取模型异步 Action 的执行状态。
-
-```js
-function FunctionComponent() {
- const actions = useModelActions('name');
- const actionsState = useModelActionsState('name');
-
- useEffect(() => {
- actions.fetch();
- }, []);
-
- actionsState.fetch.isLoading // 异步 Action 是否在执行中
- actionsState.fetch.error // 异步 Action 执行是否有误,注意仅当 isLoading 为 false 时这个值才有意义
-}
-```
-
-#### withModel
-
-`withModel(name: string, mapModelToProps?: (model: [state, actions]) => Object = (model) => ({ [name]: model }) ): (React.Component) => React.Component`
-
-使用 withModel 来连接模型和 Class Component。
-
-```jsx
-class TodoList extends Component {
- render() {
- const { counter } = this.props;
- const [ state, actions ] = counter;
- const { dataSource } = state;
-
- state.value; // 0
-
- actions.add(1);
- }
-}
-
-export default withModel('counter')(TodoList);
-```
-
-## 浏览器支持
-
-| ![Chrome](https://raw.github.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png) | ![Firefox](https://raw.github.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png) | ![Edge](https://raw.github.com/alrra/browser-logos/master/src/edge/edge_48x48.png) | ![IE](https://raw.github.com/alrra/browser-logos/master/src/archive/internet-explorer_9-11/internet-explorer_9-11_48x48.png) | ![Safari](https://raw.github.com/alrra/browser-logos/master/src/safari/safari_48x48.png) | ![Opera](https://raw.github.com/alrra/browser-logos/master/src/opera/opera_48x48.png) | ![UC](https://raw.github.com/alrra/browser-logos/master/src/uc/uc_48x48.png) |
-| :--------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------: | :--------------------------------------------------------------------------: |
-|✔ |✔|✔|9+ ✔|✔|✔|✔|
-
-## 灵感
-
-创造 icestore 的灵感来源于 [constate](https://github.com/diegohaz/constate) 和 [rematch](https://github.com/rematch/rematch)。
-
-## 贡献
-
-欢迎[反馈问题](https://github.com/alibaba/ice/issues/new)。如果对 icestore 感兴趣,请参考 [CONTRIBUTING.md](https://github.com/alibaba/ice/blob/master/.github/CONTRIBUTING.md) 学习如何贡献代码。
-
-## 协议
-
-[MIT](LICENSE)
-
diff --git a/docs/api.md b/docs/api.md
new file mode 100644
index 00000000..61a15e7d
--- /dev/null
+++ b/docs/api.md
@@ -0,0 +1,460 @@
+---
+id: api
+title: API
+---
+
+The `createStore` is a main function exported from the library, which creates a provider and other hooks.
+
+## createStore
+
+`createStore(models)`
+
+The function called to create a store.
+
+```js
+import { createStore } from '@ice/store';
+
+const {
+ Provider,
+ useModel,
+ useModelActions,
+ useModelActionsState,
+ withModel,
+ withModelActions,
+ withModelActionsState,
+} = createStore(models);
+```
+
+### models
+
+```js
+import { createStore } from '@ice/store'
+
+const counterModel = {
+ state: {
+ value: 0,
+ },
+};
+
+const models = {
+ counter: counterModel,
+};
+
+createStore(models);
+```
+
+#### state
+
+`state: any`: Required
+
+The initial state of the model.
+
+```js
+const model = {
+ state: { loading: false },
+};
+```
+
+#### actions
+
+`actions: { [string]: (prevState, payload, actions, globalActions) => any }`
+
+An object of functions that change the model's state. These functions take the model's previous state and a payload, and return the model's next state.
+
+```js
+const counter = {
+ state: 0,
+ actions: {
+ add: (prevState, payload) => prevState + payload,
+ },
+};
+```
+
+Actions provide a simple way of handling async actions when used with async/await:
+
+```js
+const counter = {
+ actions: {
+ async addAsync(prevState, payload) => {
+ await delay(1000);
+ return prevState + payload;
+ },
+ },
+};
+```
+
+You can call another action by useing `actions` or `globalActions`:
+
+```js
+const user = {
+ state: {
+ foo: [],
+ },
+ actions: {
+ like(prevState, payload, actions, globalActions) => {
+ actions.foo(payload); // call user's actions
+ globalActions.user.foo(payload); // call actions of another model
+
+ // do something...
+
+ return {
+ ...prevState,
+ };
+ },
+ foo(prevState, id) {
+ // do something...
+
+ return {
+ ...prevState,
+ };
+ },
+ },
+};
+```
+
+### Provider
+
+`Provider(props: { children, initialStates })`
+
+Exposes the store to your React application, so that your components will be able to consume and interact with the store via the hooks.
+
+```jsx
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { createStore } from '@ice/store';
+
+const { Provider } = createStore(models);
+ReactDOM.render(
+
+
+ ,
+ rootEl
+);
+```
+
+Set initialStates:
+
+```jsx
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { createStore } from '@ice/store';
+
+const models = {
+ todo: {
+ state: {},
+ },
+ user: {
+ state: {},
+ }
+};
+const { Provider } = createStore(models);
+
+const initialStates = {
+ todo: {
+ title: 'Foo',
+ done: true,
+ },
+ user: {
+ name: 'Alvin',
+ age: 18,
+ },
+};
+
+ReactDOM.render(
+
+
+ ,
+ rootEl
+);
+```
+
+### useModel
+
+`useModel(name: string): [ state, actions ]`
+
+A hook granting your components access to the model instance.
+
+```jsx
+const counter = {
+ state: {
+ value: 0,
+ },
+ actions: {
+ add: (prevState, payload) => ({...prevState, value: prevState.value + payload}),
+ },
+};
+
+const { userModel } = createStore({ counter });
+
+function FunctionComponent() {
+ const [ state, actions ] = userModel('name');
+
+ state.value; // 0
+
+ actions.add(1); // state.value === 1
+}
+```
+
+### useModelActions
+
+`useModelActions(name: string): actions`
+
+A hook granting your components access to the model actions.
+
+```js
+function FunctionComponent() {
+ const actions = useModelActions('name');
+ actions.add(1);
+}
+```
+
+### useModelActionsState
+
+`useModelActionsState(name: string): { [actionName: string]: { isLoading: boolean, error: Error } } `
+
+A hook granting your components access to the action state of the model.
+
+```js
+function FunctionComponent() {
+ const actions = useModelActions('foo');
+ const actionsState = useModelActionsState('foo');
+
+ useEffect(() => {
+ actions.fetch();
+ }, []);
+
+ actionsState.fetch.isLoading;
+ actionsState.fetch.error;
+}
+```
+
+### withModel
+
+`withModel(name: string, mapModelToProps?: (model: [state, actions]) => Object = (model) => ({ [name]: model }) ): (React.Component) => React.Component`
+
+Use withModel to connect the model and class component:
+
+```jsx
+import { UseModelValue } from '@ice/store';
+import todosModel from '@/models/todos';
+import store from '@/store';
+
+interface Props {
+ todos: UseModelValue; // `withModel` automatically adds the name of the model as the property
+}
+
+class TodoList extends Component {
+ render() {
+ const { counter } = this.props;
+ const [ state, actions ] = counter;
+
+ state.value; // 0
+
+ actions.add(1);
+ }
+}
+
+export default withModel('counter')(TodoList);
+```
+
+use `mapModelToProps` to set the property:
+
+```tsx
+import { UseModelValue } from '@ice/store';
+import todosModel from '@/models/todos';
+import store from '@/store';
+
+const { withModel } = store;
+
+interface Props {
+ title: string;
+ customKey: UseModelValue;
+}
+
+class TodoList extends Component {
+ render() {
+ const { title, customKey } = this.props;
+ const [ state, actions ] = customKey;
+
+ state.field; // get state
+ actions.add({ /* ... */}); // run action
+ }
+}
+
+export default withModel(
+ 'todos',
+
+ // mapModelToProps: (model: [state, actions]) => Object = (model) => ({ [modelName]: model }) )
+ (model) => ({
+ customKey: model,
+ })
+)(TodoList);
+```
+
+### withModelActions
+
+`withModelActions(name: string, mapModelActionsToProps?: (actions) => Object = (actions) => ({ [name]: actions }) ): (React.Component) => React.Component`
+
+```tsx
+import { ModelActionsState, ModelActions } from '@ice/store';
+import todosModel from '@/models/todos';
+import store from '@/store';
+
+const { withModelActions } = store;
+
+interface Props {
+ todosActions: ModelActions; // `withModelActions` automatically adds `${modelName}Actions` as the property
+}
+
+class TodoList extends Component {
+ render() {
+ const { todosActions } = this.props;
+
+ todosActions.add({ /* ... */}); // run action
+ }
+}
+
+export default withModelActions('todos')(TodoList);
+```
+
+You can use `mapModelActionsToProps` to set the property as the same way like `mapModelToProps`.
+
+### withModelActionsState
+
+`withModelActionsState(name: string, mapModelActionsStateToProps?: (actionsState) => Object = (actionsState) => ({ [name]: actionsState }) ): (React.Component) => React.Component`
+
+```tsx
+import { ModelActionsState, ModelActions } from '@ice/store';
+import todosModel from '@/models/todos';
+import store from '@/store';
+
+const { withModelActionsState } = store;
+
+interface Props {
+ todosActionsState: ModelActionsState; // `todosActionsState` automatically adds `${modelName}ActionsState` as the property
+}
+
+class TodoList extends Component {
+ render() {
+ const { todosActionsState } = this.props;
+
+ todosActionsState.add.isLoading; // get action state
+ }
+}
+
+export default withModelActionsState('todos')(TodoList);
+```
+
+You can use `mapModelActionsStateToProps` to set the property as the same way like `mapModelToProps`.
+
+## createModel
+
+`createStore(model)`
+
+The function called to create a model.
+
+
+```js
+import { createModel } from '@ice/store';
+
+const [
+ Provider,
+ useState,
+ useActions,
+ useActionsState,
+] = createModel(model);
+```
+
+### Provider
+
+`Provider(props: { children, initialState })`
+
+Exposes the model to your React application, so that your components will be able to consume and interact with the model via the hooks.
+
+```jsx
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { createStore } from '@ice/store';
+
+const { Provider } = createModel(model);
+ReactDOM.render(
+
+
+ ,
+ rootEl
+);
+```
+
+Set initialState:
+
+```jsx
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { createStore } from '@ice/store';
+
+const userModel = {
+ state: {},
+};
+const { Provider } = createModel(userModel);
+ReactDOM.render(
+
+
+ ,
+ rootEl
+);
+```
+
+#### useState
+
+`useState(): state`
+
+A hook granting your components access to the model state.
+
+```jsx
+const counter = {
+ state: {
+ value: 0,
+ },
+};
+
+const [, useState] = createModel(counter);
+
+function FunctionComponent() {
+ const state = useState();
+
+ state.value; // 0
+}
+```
+
+### useActions
+
+`useActions(): actions`
+
+A hook granting your components access to the model actions.
+
+```js
+function FunctionComponent() {
+ const actions = useActions();
+ actions.add(1);
+}
+```
+
+### useActionsState
+
+`useActionsState(): { [actionName: string]: { isLoading: boolean, error: Error } } `
+
+A hook granting your components access to the action state of the model.
+
+```js
+function FunctionComponent() {
+ const actions = useActions();
+ const actionsState = useActionsState();
+
+ useEffect(() => {
+ actions.fetch();
+ }, []);
+
+ actionsState.fetch.isLoading;
+ actionsState.fetch.error;
+}
+```
diff --git a/docs/recipes.md b/docs/recipes.md
new file mode 100644
index 00000000..54e04438
--- /dev/null
+++ b/docs/recipes.md
@@ -0,0 +1,206 @@
+---
+id: recipes
+title: Recipes
+---
+
+## Model interaction
+
+Model interaction is a common usage scene which can be implemented by calling actions from other model in a model's action.
+
+### Example
+
+Suppose you have a User Model, which records the number of tasks of the user. And a Tasks Model, which records the task list of the system. Every time a user adds a task, user's task number needs to be updated.
+
+```tsx
+// src/models/user
+export default {
+ state: {
+ name: '',
+ tasks: 0,
+ },
+ actions: {
+ async refresh() {
+ return await fetch('/user');
+ },
+ },
+};
+
+// src/models/tasks
+import { user } from './user';
+
+export default {
+ state: [],
+ actions: {
+ async refresh() {
+ return await fetch('/tasks');
+ },
+ async add(prevState, task, actions, globalActions) {
+ await fetch('/tasks/add', task);
+
+ // Retrieve user information after adding tasks
+ await globalActions.user.refresh();
+
+ // Retrieve todos after adding tasks
+ await actions.refresh();
+
+ return { ...prevState };
+ },
+ }
+};
+
+// src/store
+import { createStore } from '@ice/store';
+import task from './model/task';
+import user from './model/user';
+
+export default createStore({
+ task,
+ user,
+});
+```
+
+### Pay attention to circular dependencies
+
+Please pay attention to circular dependencies problem when actions calling each other between models.
+
+For example, the action A in Model A calls the action B in Model B and the action B in Model B calls the action A in Model A will results into an endless loop.
+
+Be careful the possibility of endless loop problem will arise when methods from different models call each other.
+
+## Async actions' executing status
+
+`icestore` has built-in support to access the executing status of async actions. This enables users to have access to the isLoading and error executing status of async actions without defining extra state, making the code more consise and clean.
+
+### Example
+
+```js
+import { useModelActions } from './store';
+
+function FunctionComponent() {
+ const actions = useModelActions('name');
+ const actionsState = useModelActionsState('name');
+
+ useEffect(() => {
+ actions.fetch();
+ }, []);
+
+ actionsState.fetch.isLoading;
+ actionsState.fetch.error;
+}
+```
+
+## Class Component Support
+
+You can also using icestore with Class Component. The `withModel()` function connects a Model to a React component.
+
+### Basic
+
+```tsx
+import { UseModelValue } from '@ice/store';
+import todosModel from '@/models/todos';
+import store from '@/store';
+
+const { withModel } = store;
+
+interface MapModelToProp {
+ todos: UseModelValue; // `withModel` automatically adds the name of the model as the property
+}
+
+interface Props extends MapModelToProp {
+ title: string; // custom property
+}
+
+class TodoList extends Component {
+ render() {
+ const { title, todos } = this.props;
+ const [ state, actions ] = todos;
+
+ state.field; // get state
+ actions.add({ /* ... */}); // run action
+ }
+}
+
+export default withModel('todos')(TodoList);
+```
+
+### With multiple models
+
+```tsx
+import { UseModelValue } from '@ice/store';
+import todosModel from '@/models/todos';
+import userModel from '@/models/user';
+import store from '@/store';
+
+const { withModel } = store;
+
+interface Props {
+ todos: UseModelValue;
+ user: UseModelValue;
+}
+
+class TodoList extends Component {
+ render() {
+ const { todos, user } = this.props;
+ const [ todoState, todoActions ] = todos;
+ const [ userState, userActions ] = user;
+ }
+}
+
+export default withModel('user')(
+ withModel('todos')(TodoList)
+);
+
+// functional flavor:
+import compose from 'lodash/fp/compose';
+export default compose(withModel('user'), withModel('todos'))(TodoList);
+```
+
+### withModelActions & withModelActionsState
+
+You can use `withModelActions` to call only model actions without listening for model changes, also for `withModelActionsState`.
+
+See [docs/api](./api.md) for more details.
+
+## Directory organization
+
+For most small and medium-sized projects, it is recommended to centrally manage all the project models in the global `src/models/` directory:
+
+```bash
+├── src/
+│ ├── components/
+│ │ └── NotFound/
+│ ├── pages/
+│ │ └── Home
+│ ├── models/
+│ │ ├── modelA.js
+│ │ ├── modelB.js
+│ │ ├── modelC.js
+│ │ └── index.js
+│ └── store.js
+```
+
+If the project is relatively large, or more likely to follow the page maintenance of the store,then you can declare a store instance in each page directory. However, in this case, cross page store calls should be avoided as much as possible.
+
+## Comparison
+
+- O: Yes
+- X: No
+- +: Tips
+
+| | constate | zustand | react-tracked | rematch | icestore |
+| --------| -------- | -------- | -------- | -------- | -------- |
+| Simplicity | ★★★★ | ★★★ | ★★★ | ★★★★ | ★★★★★ |
+| Readability | ★★★ | ★★★ | ★★★ | ★★★ | ★★★★ |
+| Configurable | ★★★ | ★★★ | ★★★ | ★★★★★ | ★★★ |
+| Less boilerplate | ★★ | ★★★ | ★★★ | ★★★★ | ★★★★★ |
+| Async Action | + | O | O | O | O |
+| Class Component | + | + | + | O | O |
+| Hooks Component | O | O | O | O | O |
+| Async Status | X | X | X | O | O |
+| Centralization | X | X | X | O | O |
+| Model interaction | + | + | + | O | O |
+| SSR | O | X | O | O | O |
+| Lazy load models | + | + | + | O | O |
+| Middleware or Plug-in | X | O | X | O | X |
+| Devtools | X | O | X | O | X |
+
\ No newline at end of file
diff --git a/examples/counter/.gitgnore b/examples/counter/.gitgnore
new file mode 100644
index 00000000..378eac25
--- /dev/null
+++ b/examples/counter/.gitgnore
@@ -0,0 +1 @@
+build
diff --git a/examples/counter/package.json b/examples/counter/package.json
new file mode 100644
index 00000000..80effecf
--- /dev/null
+++ b/examples/counter/package.json
@@ -0,0 +1,34 @@
+{
+ "name": "counter",
+ "version": "1.0.0",
+ "private": true,
+ "dependencies": {
+ "@ice/store": "^1.0.0",
+ "react": "^16.8.6",
+ "react-dom": "^16.8.6"
+ },
+ "devDependencies": {
+ "@types/jest": "^24.0.0",
+ "@types/node": "^12.0.0",
+ "@types/react": "^16.9.0",
+ "@types/react-dom": "^16.9.0",
+ "react-scripts": "3.4.0",
+ "typescript": "^3.7.5"
+ },
+ "scripts": {
+ "start": "react-scripts start",
+ "build": "react-scripts build"
+ },
+ "browserslist": {
+ "production": [
+ ">0.2%",
+ "not dead",
+ "not op_mini all"
+ ],
+ "development": [
+ "last 1 chrome version",
+ "last 1 firefox version",
+ "last 1 safari version"
+ ]
+ }
+}
diff --git a/examples/counter/public/index.html b/examples/counter/public/index.html
new file mode 100644
index 00000000..492089d1
--- /dev/null
+++ b/examples/counter/public/index.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+ Counter App
+
+
+
+
+
+
+
diff --git a/examples/counter/src/index.tsx b/examples/counter/src/index.tsx
new file mode 100644
index 00000000..d97cf8d2
--- /dev/null
+++ b/examples/counter/src/index.tsx
@@ -0,0 +1,51 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { createStore } from '@ice/store';
+
+const delay = (time) => new Promise((resolve) => setTimeout(() => resolve(), time));
+
+// 1️⃣ Use a model to define your store
+const counter = {
+ state: 0,
+ actions: {
+ increment:(prevState) => prevState + 1,
+ async decrement(prevState) {
+ await delay(1000);
+ return prevState - 1;
+ },
+ },
+};
+
+const models = {
+ counter,
+};
+
+// 2️⃣ Create the store
+const store = createStore(models);
+
+// 3️⃣ Consume model
+const { useModel } = store;
+function Counter() {
+ const [ count, actions ] = useModel('counter');
+ const { increment, decrement } = actions;
+ return (
+
+ {count}
+ +
+ -
+
+ );
+}
+
+// 4️⃣ Wrap your components with Provider
+const { Provider } = store;
+function App() {
+ return (
+
+
+
+ );
+}
+
+const rootElement = document.getElementById('root');
+ReactDOM.render( , rootElement);
diff --git a/examples/counter/src/react-app-env.d.ts b/examples/counter/src/react-app-env.d.ts
new file mode 100644
index 00000000..30da8962
--- /dev/null
+++ b/examples/counter/src/react-app-env.d.ts
@@ -0,0 +1 @@
+// /
diff --git a/examples/counter/tsconfig.json b/examples/counter/tsconfig.json
new file mode 100644
index 00000000..171592f7
--- /dev/null
+++ b/examples/counter/tsconfig.json
@@ -0,0 +1,25 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "strict": false,
+ "forceConsistentCasingInFileNames": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react"
+ },
+ "include": [
+ "src"
+ ]
+}
diff --git a/examples/todos/ice.config.js b/examples/todos/ice.config.js
deleted file mode 100644
index f905f88e..00000000
--- a/examples/todos/ice.config.js
+++ /dev/null
@@ -1,30 +0,0 @@
-const path = require('path');
-
-module.exports = {
- entry: 'src/index.tsx',
- publicPath: './',
- alias: {
- '@': path.resolve(__dirname, './src'),
- },
- plugins: [
- ['ice-plugin-fusion', {
- themePackage: '@icedesign/theme',
- }],
- ['ice-plugin-moment-locales', {
- locales: ['zh-cn'],
- }],
- ],
- chainWebpack: (config) => {
- // 修改对应 css module的 loader,默认修改 scss-module 同理可以修改 css-module 和 less-module 规则
- ['scss-module'].forEach((rule) => {
- if (config.module.rules.get(rule)) {
- config.module.rule(rule).use('ts-css-module-loader')
- .loader(require.resolve('css-modules-typescript-loader'))
- .options({ modules: true, sass: true });
- // 指定应用loader的位置
- config.module.rule(rule).use('ts-css-module-loader').before('css-loader');
- }
- });
- },
-};
-
diff --git a/examples/todos/package.json b/examples/todos/package.json
index d0cf0249..e536045d 100644
--- a/examples/todos/package.json
+++ b/examples/todos/package.json
@@ -4,18 +4,33 @@
"private": true,
"dependencies": {
"@ice/store": "^1.0.0",
- "react": "16.8.6",
- "react-dom": "16.8.6"
+ "lodash": "^4.17.15",
+ "react": "^16.8.6",
+ "react-dom": "^16.8.6"
},
"devDependencies": {
- "css-modules-typescript-loader": "^2.0.4",
- "ice-plugin-fusion": "^0.1.4",
- "ice-plugin-moment-locales": "^0.1.0",
- "ice-scripts": "^2.0.0"
+ "@types/jest": "^24.0.0",
+ "@types/node": "^12.0.0",
+ "@types/react": "^16.9.0",
+ "@types/react-dom": "^16.9.0",
+ "react-scripts": "3.4.0",
+ "typescript": "^3.7.5",
+ "utility-types": "^3.10.0"
},
"scripts": {
- "start": "ice-scripts dev",
- "build": "ice-scripts build",
- "test": "ice-scripts test"
+ "start": "react-scripts start",
+ "build": "react-scripts build"
+ },
+ "browserslist": {
+ "production": [
+ ">0.2%",
+ "not dead",
+ "not op_mini all"
+ ],
+ "development": [
+ "last 1 chrome version",
+ "last 1 firefox version",
+ "last 1 safari version"
+ ]
}
}
diff --git a/examples/todos/public/index.html b/examples/todos/public/index.html
index 5815fc96..552eacb1 100644
--- a/examples/todos/public/index.html
+++ b/examples/todos/public/index.html
@@ -5,11 +5,11 @@
- todos app
+ Todos App
-
+