具有联合类型的通用抛出不可分配的错误

dav*_*000 12 generics typescript redux

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

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'
Run Code Online (Sandbox Code Playgroud)

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

这是打字稿游乐场

这是完整的可重现代码

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;
};
Run Code Online (Sandbox Code Playgroud)

Tyl*_*ian 6

几件事:

第一的,

Array<A | B>
(A | B)[]
Run Code Online (Sandbox Code Playgroud)

都是相同的。

其次,之所以A可以分配给两者,是因为 A 的所有属性也在 B 中。

第三,不要改变状态。重新分配它还不够。

> const x = {}
undefined
> const y = x
undefined
> y.a = 1
1
> x
{ a: 1 }
Run Code Online (Sandbox Code Playgroud)

您可以 splat 进入一个新对象:let newState = { ...state }- 这通常就足够了。

好的。您不能将 type 的值分配A | B给 type 的值B。您使用了其他内容 ( type) 来表示不同的值,但 TS 无法知道这一点,除非您告诉它。您可以通过多种不同的方式来做到这一点。

首先,断言:

newState.b = action.data as B[];
Run Code Online (Sandbox Code Playgroud)

这实际上是在告诉 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;
}
Run Code Online (Sandbox Code Playgroud)

注意:我实际上无法让它工作......代码是正确的,我只是never在第一次检查后获取操作类型 - 不确定这里发生了什么)

最后,让 TypeScript 通过 duck-typing 为您解决问题

简而言之,如果对象中有一个与类型相关的属性,那么 TS 可以自动选择类型(如果该属性足够唯一)。


aqu*_*quz 6

我怀疑这个错误是由于两个接口具有相似的值,除了 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;
Run Code Online (Sandbox Code Playgroud)

编辑:(我不能写评论)

@泰勒塞巴斯蒂安

Array<A | B>
(A | B)[]
Run Code Online (Sandbox Code Playgroud)

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