typescript 如何整齐地做一半的一个型结合所需?

bprjcwpo  于 2023-03-04  发布在  TypeScript
关注(0)|答案(1)|浏览(118)

在我们的应用程序中有两个子类型:

export interface QueuedAction extends Action {
  meta: {
    queueName: string;
    overwrite: boolean;
  }
}

export interface BroadcastAction extends Action {
  meta: {
    channelName: string;
  }
}

基于typescript interface require one of two properties to exist,我将它们合并如下:

export type MetaAction = QueuedAction | BroadcastAction;

这很好,但是有时候我需要对MetaAction进行操作,其中包含联合的一半是有保证的。
这有以下问题:

const wrapBroadcast = (action: AnyAction, channelName = 'DEFAULT_CHANNEL'): MetaAction => ({
  ...action,
  meta: { ...action.meta, channelName },
});

const unwrapBroadcast = (action: MetaAction) => {
  if (!action.meta) return action;

  const { channelName, ...rest } = action.meta; // channelName does not exist on type '{ queueName: string, overwrite?: boolean} | { channelName: string }'
  const unwrappedAction = { ...action, meta: rest };
  return unwrappedAction;
}

如果我把它从MetaAction切换到BroadcastAction | {BroadcastAction & MetaAction}(也就是说,它总是会有channelName,但 meta的其余部分是灵活的),我得到这个错误,当我去使用它:
类型为“MetaAction”的参数不能赋给类型为“BroadcastAction & MetaAction”的参数
直到我交换wrapAction的返回类型以匹配。
有没有一种更通用的方法来声明BroadcastAction | {BroadcastAction & MetaAction},如果将来我们可能会在“ meta”中使用更多的东西,并且我们在其他地方需要这个模式?

n6lpvg4x

n6lpvg4x1#

使MetaAction成为泛型

使这个模式更通用的简单方法是使它...通用:

export type MetaAction<T extends { meta: any } | {} = {}> = T & (QueuedAction | BroadcastAction);

也就是说,MetaAction接受一个 option 参数T,该参数必须扩展任何带有{ meta: any }或{}的内容。
为了获得更高的类型安全性,请使特定的MetaAction扩展Base类型:

// no meta specified
interface BaseMetaAction extends Action {};

export QueuedAction extends BaseMetaAction {
  // as before
}

export BroadcastAction extends BaseMetaAction {
  // as before
}

export type MetaAction<T extends BaseMetaAction = Action> = T & (QueuedAction | BroadcastAction);

现在,您可以拥有具有或不具有所需子类型的MetaAction,只要该子类型扩展BaseMetaAction即可。
以下是你会使用和不会使用它的方式:

// does work
const myQueuedAction: MetaAction<QueuedAction> = {
  type: 'foo':
  meta: {
    queueName: 'THE_FOO_QUEUE',
  }
};

const myQueuedSometimesBroadcastAction: MetaAction<QueuedAction> = {
  type: 'foo':
  meta: {
    queueName: 'THE_FOO_QUEUE',
    channelName: 'THE_FOO_BROADCAST',
  }
};

const myBroadcastSometimesQueuedAction: MetaAction<BroadcastAction> = {
  type: 'foo':
  meta: {
    queueName: 'THE_FOO_QUEUE',
    channelName: 'THE_FOO_BROADCAST',
  }
};

// does not work
const myQueuedSometimesBroadcastAction: MetaAction<QueuedAction> = {
  type: 'foo':
  meta: {
    channelName: 'THE_FOO_BROADCAST',
  }
};

const myBroadcastSometimesQueuedAction: MetaAction<BroadcastAction> = {
  type: 'foo':
  meta: {
    queueName: 'THE_FOO_QUEUE',
  }
};

// constraint not satisfied
type NotASubtype = { meta: {meta: "I'm so meta even this acronym" } }
const myWildMetaAction: MetaAction<NotASubtype> = {
  type: 'foo':
  meta: {
    queueName: 'THE_FOO_QUEUE',
  }
};

相关问题