强制许多可选属性中的一个在类型的实例中
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
}
project
和 err
是可选的。
我希望他们保留他们的名称道具,以便可以在类型实例中传入或要求它们,但是,强制拥有其中之一,这样这将作为两个可选 project
或 err
需要传入。
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 >
T
被限制为 { type: ActionType }
因此我们能够索引 T["type"]
OnlyDefined<R>[P]
为我们提供了 T
中应该存在的正确 属性
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
我有这种类型或接口:
export interface CreateProjectAction {
type: typeof CREATE_PROJECT | typeof CREATE_PROJECT_ERROR
project?: {title:string, content:string}
err?: Error
}
project
和 err
是可选的。
我希望他们保留他们的名称道具,以便可以在类型实例中传入或要求它们,但是,强制拥有其中之一,这样这将作为两个可选 project
或 err
需要传入。
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 >
T
被限制为{ type: ActionType }
因此我们能够索引T["type"]
OnlyDefined<R>[P]
为我们提供了T
中应该存在的正确 属性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