状态管理

icejs 内置集成了 icestore 状态管理方案,并在此基础上进一步遵循 “约定优于配置” 原则,进行抽象和封装,使得状态管理变得非常容易。

目录约定

icejs 支持 全局状态页面状态 两种纬度:

  • 全局状态:应用级别,整个应用都可以使用
  • 页面状态:只针对单个页面,比如 src/pages/Home,仅在 Home/ 中可以被使用

目录组织如下:

  1. src
  2. ├── models // 全局状态:通常会有多个 model
  3. | ├── counter.ts
  4. └── user.ts
  5. └── pages
  6. | ├── Home
  7. | ├── index.tsx
  8. | └── model.ts // 页面级状态:通常只有一个 model
  9. | ├── About
  10. | ├── index.tsx
  11. | └── model.ts
  12. └── app.ts

状态声明

状态定义方式如下:

  1. // src/models/counter.ts
  2. export const delay = (time) => new Promise((resolve) => setTimeout(() => resolve(), time));
  3. export default {
  4. state: {
  5. count: 0
  6. },
  7. reducers: {
  8. increment(prevState) {
  9. return { count: prevState.count + 1 }
  10. },
  11. decrement(prevState) {
  12. return { count: prevState.count - 1 }
  13. }
  14. },
  15. effects: {
  16. async decrementAsync(state, payload, actions) {
  17. await delay(10);
  18. actions.decrement();
  19. },
  20. }
  21. };

视图绑定状态

定义好全局状态和页面级状态后,在视图中即可获取定义好的数据:

  1. // pages/Home/index.jsx
  2. import { store as appStore } from 'ice';
  3. import { store as pageStore } from 'ice/Home';
  4. const HomePage = () => {
  5. // 1. 全局状态:model 名称即文件名称,如 src/models/counter.ts -> counter
  6. const [ counterState, counterActions ] = appStore.useModel('counter')
  7. // 2. 页面状态:一个 model 的情况 model 名称约定为 default, 如 src/pages/*/model.ts -> default
  8. // const [ pageState, pageAction ] = pageStore.useModel('default');
  9. // 3. 页面状态:多个 model 的情况,model 名称即文件名,如 src/pages/*/models/foo.ts -> foo
  10. // const [ fooState, fooAction ] = pageStore.useModel('foo');
  11. return (
  12. <>
  13. <button type="button" onClick={counterActions.increment}>+</button>
  14. <span>{counterState.count}</span>
  15. <button type="button" onClick={counterActions.decrementAsync}>-</button>
  16. </>
  17. );
  18. }

大部分情况下,按照上面两个步骤的操作就可以在项目里正常的使用状态管理能力了。

高阶能力

仅使用 Action 不使用 State

有些时候组件中只需要触发 action 不需要依赖对应的数据状态,此时可以使用 useModelActions API:

  1. import { store } from 'ice';
  2. function FunctionComponent() {
  3. // 只调用 increment 方法
  4. const actions = store.useModelActions('counter');
  5. actions.increment();
  6. }

异步 Action 状态

通过 useModelEffectsState API 即可获取到异步 action 的 loading 和 error 状态:

  1. import { store } from 'ice';
  2. function FunctionComponent() {
  3. const [state, actions] = store.useModel('counter');
  4. const actionsState = store.useModelEffectsState('counter');
  5. useEffect(() => {
  6. actions.decrementAsync();
  7. }, []);
  8. actionsState.decrementAsync.isLoading;
  9. actionsState.decrementAsync.error;
  10. }

Action 联动

action 方法第三和第四个参数分别可以拿到当前 model 以及全局 model 的所有 actions:

  1. // src/models/user.ts
  2. export default {
  3. state: {
  4. name: '',
  5. id: ''
  6. },
  7. reducers: {
  8. update(prevState, payload) {
  9. return {
  10. ...prevState,
  11. ...payload,
  12. };
  13. },
  14. },
  15. effects: {
  16. async getUserInfo(prevState, payload, actions, globalActions) {
  17. // 调用 counter 模型的 decrement 方法
  18. globalActions.counter.decrement();
  19. // 调用当前模型的 update 方法
  20. actions.update({
  21. name: 'taobao',
  22. id: '123',
  23. });
  24. },
  25. },
  26. };

在 Class Component 中使用

useModel 相关的 API 基于 React 的 Hooks 能力,仅能在 Function Component 中使用,通过 withModel API 可以实现在 Class Component 中使用:

  1. import { store } from 'ice';
  2. class TodoList extends React.Component {
  3. render() {
  4. const { todos } = this.props;
  5. const [ state, actions ] = todos;
  6. state.todos;
  7. actions.add({ /* ... */});
  8. }
  9. }
  10. export default store.withModel('todos')(TodoList);
  11. // 绑定多个 model
  12. // export default withModel('user')(withModel('todos')(TodoList));

同时,也可以使用 withModelActions 以及 withModelEffectsState API。