某些对象的类型定义有点不稳定。我知道属性和类型,但编译器不知道。我想写一个小函数,它提取具有我期望的类型的属性,但如果类型错误则抛出错误。
我想知道readProperty"下面的函数可以以某种方式告诉编译器,例如提取的属性是一个数字,因为开发人员写了&;number&;当调用函数时
这可能吗?
function readProperty<T>(obj: T, key: keyof T, typeName: "string" | "number"): string | number {
const value = obj[key]
if (typeof value != typeName) {
throw new Error(`Property ${key.toString()} must be a ${typeName}`)
}
return value
}
const someObj = {
x: "123",
y: 123,
}
const x = readProperty(someObj, "x", "number") // <-- compiler should "know" now that x is a number
const y = readProperty(someObj, "y", "string") // <-- compiler should "know that y is a string
对于这个用例,我认为在obj
类型或key
类型中使函数泛型没有多大用处。如果编译器实际上对obj
和key
有足够的了解,这样一个泛型调用签名是有用的,那么您就不需要对属性类型做任何进一步的检查(或者更糟的是,编译器将不同意您的类型)。
相反,重要的部分是获得调用签名,以便当您传递字符串文字类型"string"
作为typeName
时,该函数的输出类型为string
,如果您传递"number"
,则输出类型为number
。表示字符串文字输入类型和任意输出类型之间的映射的最直接的方法是使用像interface
这样的对象类型,然后查找输出属性涉及到输入字符串文字上的索引访问类型。这样的:
interface TypeofMap {
string: string;
number: number;
}
function readProperty<K extends keyof TypeofMap>(
obj: object, key: PropertyKey, typeName: K
): TypeofMap[K] {
const value = (obj as any)[key]
if (typeof value != typeName) {
throw new Error(`Property ${key.toString()} must be a ${typeName}`)
}
return value
}
所以readProperty()
在K
中是泛型的,typeName
的类型被约束为TypeofMap
接口的键之一…"string"
或者"number"
。然后函数的返回类型是TypeofMap[K]
,对应的类型是string
或number
。
注意,编译器不能真正验证readProperty
的实现是否符合呼叫签名。因此,我通过(obj as any)
断言obj
是any
类型,以放松函数体内部的类型检查,以防止错误。这意味着您需要小心实现做正确的事情。如果您将(typeof value != typeName)
更改为(typeof value == typeName)
,编译器不会注意到或抱怨。所以保重。
无论如何,让我们看看它是否在调用方工作:
const x = readProperty(someObj, "x", "number");
// ^? const x: number
const y = readProperty(someObj, "y", "string");
// ^? const y: string
看起来不错!
Playground链接到代码
已经有人回答了这个问题,但我想分享我最终得到的实际代码。不同之处在于我为obj
参数使用了泛型类型。这使得编译器(和IDE)知道key
参数可以使用哪些值。
export interface IPropertyObject {
number: number
string: string
boolean: boolean
}
export function readProperty<T, K extends keyof IPropertyObject>(
obj: T,
key: keyof T,
typeName: K
): IPropertyObject[K] {
const value = (obj as any)[key]
if (typeof value != typeName) {
throw new Error(`Property ${key.toString()} must be a ${typeName}`)
}
return value
}
const someObj = { x: 123 }
const x = readProperty(someObj, "x", "number");
// ^ x is a valid property key for the "someObj" object