[英]Typescript typecast object so specific required keys are no longer optional in the type?
假设您有一个 object 类型:
Type Person = {
name?: string;
color?: string;
address? string;
}
但是,您想将该类型更改为以下类型,您知道名称和颜色将存在。
Type Person = {
name: string;
color: string;
address? string;
}
因此,有function
const throwIfUndefined = (
object: {[key: string]: any},
requiredKeys: string[]
): ReturnTypeHere => {
for (const key of requiredKeys) {
if (!object[key]) throw new Error("missing required key");
}
return object;
};
输入 function 的参数以及返回类型( ReturnTypeHere
)的正确方法是什么? 写得正确,下面将要么 1)抛出错误 2)控制台记录名称。 它永远不会控制台未定义的日志。
const person = {...}
const requiredKeys = ["name", "color"];
const verifiedPerson = throwIfUndefined(person, requiredKeys);
console.log(verifiedPerson.name)
如果你有一个 object 类型T
和一个你想要的键K
的联合,你可以这样写RequireKeys<T, K>
:
type RequireKeys<T extends object, K extends keyof T> =
Required<Pick<T, K>> & Omit<T, K>;
这里我们使用Required<T>
、 Pick<T, K>
和Omit<T, K>
实用程序类型。 这里有可能的边缘情况,例如如果T
具有字符串索引签名并且string
出现在K
中,但对于第一个近似值,它应该可以工作。
也有点难以理解RequiredKeys<Person, "name" | "color">
RequiredKeys<Person, "name" | "color">
来自它在 IDE 中的显示方式:
type VerifiedPerson = RequireKeys<Person, "name" | "color">;
// type VerifiedPerson = Required<Pick<Person, "name" | "color">> &
// Omit<Person, "name" | "color">
如果您希望编译器更加明确,可以执行以下操作将类型扩展为其属性:
type RequireKeys<T extends object, K extends keyof T> =
(Required<Pick<T, K>> & Omit<T, K>) extends
infer O ? { [P in keyof O]: O[P] } : never;
这导致
/* type VerifiedPerson = {
name: string;
color: string;
address?: string | undefined;
} */
这更容易看到。
--
然后您需要将throwIfUndefined()
通用 function以便编译器可以跟踪传入的object
和requiredKeys
之间的关系:
const throwIfUndefined = <T extends object, K extends keyof T>(
object: T,
requiredKeys: readonly K[]
) => {
for (const key of requiredKeys) {
if (!object[key]) throw new Error("missing required key");
}
return object as unknown as RequireKeys<T, K> // need to assert this
};
并测试:
const person: Person = {
...Math.random() < 0.8 ? { name: "Alice" } : {},
...Math.random() < 0.8 ? { color: "Color for a person is problematic" } : {}
};
const requiredKeys = ["name", "color"] as const;
const verifiedPerson = throwIfUndefined(person,
requiredKeys); // possible runtime error here
// const verifiedPerson: RequireKeys<Person, "name" | "color">
如果您希望编译器记住文字类型"name"
和"color"
是requiredKeys
的成员,那么您需要执行类似const
断言(即as const
)之类的操作来告诉它。 否则requiredKeys
将只是string[]
并且您会得到奇怪/错误的结果(我们可以防范这些,但它可能会超出 scope 此处)。
现在,编译器知道name
和color
已定义,而address
仍然是可选的:
console.log(verifiedPerson.name.toUpperCase() + ": " +
verifiedPerson.color.toUpperCase()); // no compile error
// [LOG]: "ALICE: COLOR FOR A PERSON IS PROBLEMATIC"
verifiedPerson.address // (property) address?: string | undefined
您可以使用以下类型:
type Require<T, K extends keyof T> = T & { [P in K]-?: T[P]; }
使某些道具需要。 例如
type P = Require<Person, "name" | "color">
所以你现在可以用它来输入你的 function:
const throwIfUndefined = <T, K extends keyof T>(
object: T,
requiredKeys: K[]
): Require<T, K> => {
if(requiredKeys.some(key => typeof object[key] === "undefined"))
throw new Error("missing required key");
return object as Require<T, K>;
};
请考虑下一个示例。
我认为typeguards
是这里最合适的解决方案:
type Person = {
name?: string;
color?: string;
address?: string;
}
const hasProperty=<T,P extends string>(obj:T,prop:P):obj is T & Record<P, unknown>=>
Object.prototype.hasOwnProperty.call(obj, prop);
const throwIfUndefined = <T, K extends string>(
obj: T,
requiredKeys: K[]
): obj is T & Record<K, unknown> => requiredKeys.every(key => hasProperty(obj,key));
const person = {
name: 'John',
color: 'red',
address: 'Ternopil'
}
const check = (arg: unknown) => {
if (throwIfUndefined(arg, ['name'])) {
const x = arg.name // ok
}
if (throwIfUndefined(arg, ['name', 'age'])) {
const x = arg.name // ok
const y = arg.age // ok
const z = arg.other // error
}
}
我决定从throwIfUndefined
返回 boolean ,因为这就是 typeguardss 的工作方式。 在这里,您有完全通用的解决方案。 throwIfUndefined(arg, ['name', 'age'])
表示arg
变量 100% 包含name
和age
属性
如果你仍然想抛出错误,你可以使用assert
函数。
在这里您可以找到文档。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.