打字稿,带标签的动作和化简器数组

时间:2019-01-16 15:13:17

标签: typescript

我正在使用打字稿,正在研究大量从redux工作方式(action, state) => state借用的代码。

以尽可能严格的方式尝试使用打字稿。我的问题最好用这个例子来解释。

首先,我有一个列举动作类型的枚举:

enum Tags {
  TAG_A = 'tagA',
  TAG_B = 'tagB'
}

接下来,我定义每种类型的唯一字段:

type Payload = {
  [Tags.TAG_A]: {
    foo: string;
  },
  [Tags.TAG_B]: {
    bar: number;
  }
}

现在我定义我的操作类型:

type Action<T extends Tags> = {
  type: T;
  id: string;
  moreCommonField: boolean;
} & Payload[T];

type Actions = { [T in Tags]: Action<T> }[Tags]

现在是减速器:

type State = { somevar: string }; //doesnt matter, example

const reducer = (action: Actions, state: State): State => {
  switch (action.type) {
    case Tags.TAG_A:
      action.foo; /* correct type, yay */
      break;
    case Tags.TAG_B:
      action.bar; /* correct type, yay */
      break;
  }
  return {...state};
}

到目前为止,一切都很好!

但是在现实世界中,我有很多动作,而我的switch看起来很丑。

所以我尝试了这个:

const minireducers: { [K in Tags]: (action: Action<K>, state: State) => State } = {
  [Tags.TAG_A]: (action, state) => {
    // we have correct type of action here, big benefit
    return {...state};
  },
  [Tags.TAG_B]: (action, state) => ({...state})
}

这很棒,因为大多数“微型减速器”只是一个衬套。这还具有迫使我不要忘记处理以后可能添加的任何操作的好处。

但是,当我尝试为此编写实际的reducer时:

const reducer2 = (action: Actions, state: State) => {
  const minireducer = minireducers[action.type]; 
  const newState = minireducer(action, state); //error
  return newState;
}

这里的错误对我很清楚,action.type没有缩小为任何特定的类型,因此minireducer可以是很多东西,它的类型是联合,您不能称其为联合。使用强制转换显然可以正常工作,但是我想要一个不需要强制转换的解决方案。甚至只是一个思想实验。

任何人都对尽可能安全的解决方案有任何想法,这将允许我将减速器拆分为较小的减速器,如示例中所示?

您不必坚持我的类型定义或结构,但是这里的目标还是要确保类型安全和良好的类型推断。

link to playground

1 个答案:

答案 0 :(得分:1)

问题在于minireducer将是所有签名的并集,并且该签名不可调用。

feature的打字稿3.3可以放宽此限制,但是您的代码将无法正常工作,因为这将期望第一个参数是签名并集中所有参数的交集。

一种解决方案,如果您不介意进行额外的函数调用,则可以使用额外的函数,这会使编译器休息,使该对象具有正确的签名:

const reducer2 = (action: Actions, state: State) => {
    // r must have a property K that is a function taking Action<K> 
    function call<K extends Tags>(o: Action<K>, r: Record<K, (action: Action<K>, state: State) => State>, state: State) {
        r[o.type](o, state); // r[o.type] is  (action: Action<K>, state: State) => State which is callable with an Action<K> 
    }
    const newState = call(action, minireducers, state);
    return newState;
}