具有联合类型抛出的泛型无法分配错误

时间:2019-06-10 19:33:03

标签: typescript generics redux

我有一个reducer,它具有一个动作创建者,该动作创建者可以是两个不同类型的对象的数组,每个对象都有自己的接口。但是,我收到此错误

Type '(A | B)[]' is not assignable to type 'B[]'.
  Type 'A | B' is not assignable to type 'B'.
    Property 'productionId' is missing in type 'A' but required in type 'B'

我怀疑此错误是由于两个接口具有相似的值,除了B比A具有一个额外的值?

这是typescript playground

这是完整的可复制代码

interface A {
  id: number;
  name: string;
}

interface B {
  id: number;
  productionId: number;
  name: string;
}

interface IAction<Data> {
  type: string;
  data: Data;
}

interface ISelectionOptionsReducerState {
  a: A[];
  b: B[];
}

let initialState: ISelectionOptionsReducerState = {
  a: [],
  b: []
};

type TAction = IAction<Array<A | B>>;
type TAction = IAction<A[] | B[]>; <= this didn't work either

type TReducer = (
  state: ISelectionOptionsReducerState,
  action: TAction
) => ISelectionOptionsReducerState;

const selectionOptionsReducer: TReducer = (
  state: ISelectionOptionsReducerState = initialState,
  action: TAction
): ISelectionOptionsReducerState => {
  Object.freeze(state);

  let newState: ISelectionOptionsReducerState = state;

  switch (action.type) {
    case '1':
      newState.a = action.data;
      break;
    case '2':
      newState.b = action.data; <= error happen here
      break;
    default:
      return state;
  }

  return newState;
};

2 个答案:

答案 0 :(得分:2)

  

我怀疑此错误是由于两个接口的值相似,   除了B比A具有额外的价值?

是的,您可以将B分配给A,但不能将A分配给B。

您需要输入保护:

function isA(data: A | B): data is A {
  return typeof (data as B).productionId === 'undefined'
}

function isB(data: A | B): data is B {
  return typeof (data as B).productionId === 'string'
}

... 

case '1':
  newState.a = action.data.filter(isA);
  break;
case '2':
  newState.b = action.data.filter(isB);
  break;

编辑: (我不能写评论)

@泰勒·塞巴斯蒂安

Array<A | B>
(A | B)[]

相同,但A[] | B[]不同

答案 1 :(得分:1)

耦合事物:

首先

Array<A | B>
(A | B)[]

都一样。

第二,A可以同时分配给这两个原因是因为A的所有属性也都在B中。

第三,不要改变状态。重新分配是不够的。

> const x = {}
undefined
> const y = x
undefined
> y.a = 1
1
> x
{ a: 1 }

您可以使用新对象let newState = { ...state }-通常就足够了。

好的。您无法将A | B类型的值分配给B类型的值。您已经使用其他(type)来表示其他值,但是TS除非知道,否则不会知道。您可以通过多种不同的方式来做到这一点。

首先,断言:

newState.b = action.data as B[];

这实际上是在告诉TS窃听。通常,这很好。如果您做的事情确实值得怀疑,TS会让您先声明unknown。不过,事实并非如此。

但是有更好的方法。

稍好一点:类型卫兵

这需要重构开关:

function isA(x: any): x is IAction<Array<A>> {
  return x.type === '1'
}

function isB(x: any): x is IAction<Array<B>> {
  return x.type === '2'
}

...

if (isA(action)) {
  newState.a = action.data;
} else if (isB(action)) {
  newState.b = action.data;
}

注意:我实际上无法使它正常工作……代码正确,我只是在第一次检查后就输入never进行操作-不确定这是怎么回事)

最后,let TypeScript do the resolution for you via duck-typing

其中的tl; dr是,如果在对象中具有与类型相关联的属性,则如果该属性足够唯一,则TS可以自动选择类型。