衍生状态

一些场景中,组件需要对 Model 中的 State 进行进一步计算,才能在组件中使用,这部分逻辑可以直接写在组件内部,也可以通过 Model 的衍生状态实现。 衍生状态定义在 Model 中的 computed 字段下。根据依赖的 Model 的不同、返回类型的不同,衍生状态的定义方法可以分为以下 3 种。

只依赖自身的 State

衍生状态只依赖当前 Model 的 State,State 会作为第一个参数,传入衍生状态的定义函数中。

例如, todo 应用的 State 有 itemsfilterfilter 用于过滤当前页面显示的 todo 项,所以我们定义了一个 visibleTodos 的衍生状态可以直接在组件中使用。示例代码如下:

/**
 *  假设 todo item 结构如下:
{
    id: string;          // id
    text: string;        // todo 事项
    completed: boolean;  // 完成状态:0 代表未完成,1 代表完成
}
**/

const todoModel = model('todo').define({
  state: {
    items: [],
    filter: 'ALL', // ALL: 显示全部;COMPLETED:显示完成的事项;ACTIVE:显示未完成的事项
  },
  computed: {
    visibleTodos: state => {
      switch (state.filter) {
        case 'ALL':
          return state.items;
        case 'COMPLETED':
          return todos.filter(t => t.completed);
        case 'ACTIVE':
          return todos.filter(t => !t.completed);
        default:
          return [];
      }
    },
  },
});

衍生状态最终会和 Model 的 State 进行合并,因此,可以通过 Model 的 State 对象访问到衍生状态,例如,visibleTodos 在组件内的使用方式如下:

function Todo() {
  const [state, actions] = useModel(todoModel);

  return (
    <div>
      <div>
        {state.visibleTodos.map(item => (
          <div key={item.id}>{item.text}</div>
        ))}
      </div>
    </div>
  );
}

依赖其他 Model 的 State

除了依赖当前 Model 的 State,衍生状态还依赖其他 Model 的 State,这时候衍生状态的定义格式为:

[stateKey]: [...depModels, (selfState, ...depModels) => computedState]

下面的示例,演示了 barModel 的衍生状态 combinedValue 是如何依赖 fooModel 的 State 的。

const fooModel = model('foo').define({
  state: {
    value: 'foo',
  },
});

const barModel = model('bar').define({
  state: {
    value: 'foo',
  },
  computed: {
    combinedValue: [
      fooModel,
      (state, fooState) => state.value + fooState.value,
    ],
  },
});

函数类型的衍生状态

衍生状态还可以返回一个函数。这时候衍生状态的定义格式为:

[stateKey]: (state) => (...args) => computedState    // 只依赖自身的 state
[stateKey]: [...depModels, (selfState, ...depModels) => (...args) => computedState]  // 依赖其他 Model 的 state

假设,todo 应用的 state 不存储 filter 状态,而是直接在组件中使用,那么 visibleTodos 可以是一个函数类型的值,这个函数在组件中使用的时候,接收 filter 参数。如下所示:

const todoModel = model('todo').define({
  state: {
    items: [],
  },
  computed: {
    visibleTodos: state => filter => {
      switch (filter) {
        case 'ALL':
          return state.items;
        case 'COMPLETED':
          return todos.filter(t => t.completed);
        case 'ACTIVE':
          return todos.filter(t => !t.completed);
        default:
          return [];
      }
    },
  },
});

function Todo(props) {
  // filter 状态通过 props 传入
  const { filter } = props;
  const [state, actions] = useModel(todoModel);

  // 计算得到最终要显示的 todos
  const todos = state.visibleTodos(filter);

  return (
    <div>
      <div>
        {todos.map(item => (
          <div key={item.id}>{item.text}</div>
        ))}
      </div>
    </div>
  );
}
更多参考