强制许多可选属性中的一个在类型的实例中

Force one out of many optional properties to be in the instance of a type

我有这种类型或接口:

export interface CreateProjectAction {
  type: typeof CREATE_PROJECT | typeof CREATE_PROJECT_ERROR
  project?: {title:string, content:string}
  err?: Error
}

projecterr 是可选的。 我希望他们保留他们的名称道具,以便可以在类型实例中传入或要求它们,但是,强制拥有其中之一,这样这将作为两个可选 projecterr 需要传入。

function Foo(createAction: CreateProjectAction) {
  console.log(createAction);
}

Foo({ type: 'CREATE_PROJECT_ERROR' } ); //ERROR, need to pass in either the project or the err prop

如前所述,我想保留道具名称,因为 optionalArg 名称的描述性不强,所以我放弃了这个选项:

export interface CreateProjectAction {
  type: typeof CREATE_PROJECT | typeof CREATE_PROJECT_ERROR
  optionalArg?: {title:string, content:string} | Error
}

使用一点黑魔法,您可以根据 type 的类型强制“定义一个且唯一的 属性”。首先,我们需要在 type 联合体的成员和 CreateProjectAction 所需的属性之间建立关系。最简单的方法是创建一个查找,其中键的类型为 type,值的类型为 keyof CreateProjectAction,例如:

{
  "CREATE_PROJECT": "project", 
  "CREATE_PROJECT_ERROR" : "err"
}

接下来,让我们利用 4.1 中的 key remapping 功能(undefined 属性从查找中过滤掉):

type OnlyDefined<T> =  {
  [ P in keyof T as T[P] extends undefined ? never : P ] : T[P]
};

最后念咒:

type RequireIf<T extends { type: ActionType }, R extends { [ P in ActionType ] ?: keyof T }> = {
  [ P in keyof OnlyDefined<R> as P extends T["type"] ? OnlyDefined<R>[P] & string : never ] : OnlyDefined<R>[P] extends keyof T ? Exclude<T[OnlyDefined<R>[P]], undefined> : never
} & Omit<T,  OnlyDefined<R>[ keyof OnlyDefined<R> ] & string >
  1. T 被限制为 { type: ActionType } 因此我们能够索引 T["type"]
  2. OnlyDefined<R>[P] 为我们提供了 T 中应该存在的正确 属性
  3. Exclude<T[OnlyDefined<R>[P]], undefined> 强制定义 属性(由于允许部分查找,属性 的类型是 <type> | undefined

测试确认一切正常:

type CreateProjectAction<T extends ActionType> = RequireIf<{
  type: T
  project?: {title:string, content:string}
  err?: Error
}, {
  "CREATE_PROJECT": "project", 
  "CREATE_PROJECT_ERROR" : "err"
}>

const project: CreateProjectAction<"CREATE_PROJECT"> = { type: "CREATE_PROJECT", project: { content: "Yay!", title: "Vote" } };

const error: CreateProjectAction<"CREATE_PROJECT_ERROR"> = { type: "CREATE_PROJECT_ERROR", err: new RangeError("Nay!") };

const invalid: CreateProjectAction<"CREATE_PROJECT"> = { type: "CREATE_PROJECT", err: new Error("Nope") }; //'err' does not exist in type

Playground