如何在仅应用字符串的 TypeScript 中编写函数

How can I write function in TypeScript that apply only string

如果对象中的值具有 string 类型,我需要只允许传递键的函数:

type GetNames<FromType, KeepType = any, Include = true> = {
    [K in keyof FromType]: 
        FromType[K] extends KeepType ? 
            Include extends true ? K : 
            never : Include extends true ? 
            never : K
}[keyof FromType];

const functionOnlyForStrings = <T>(obj: T, key: GetNames<T, string>) => {
    const t = obj[key]
    // do something with strings
    return t.toUpperCase()
}


const testObj: {a: string, b: number} = {a: 'test', b: 123}

const test = functionOnlyForStrings(testObj, 'a')
const wrongParam = functionOnlyForStrings(testObj, 'b')

行数:

const test = functionOnlyForStrings(testObj, 'a') 
const wrongParam = functionOnlyForStrings(testObj, 'b') // here I get an error message

一切正常。如果我通过 b 键,TS 会显示一个错误。

但是功能有问题functionOnlyForStrings。在这个函数中,TS 不知道 obj[key] 总是字符串。并告诉我错误:

Property 'toUpperCase' does not exist on type 'T[{ [K in keyof T]: T[K] extends string ? K : never; }[keyof T]]'.

Playground

这基本上是一个 design limitation of TypeScript,至少现在是这样。

关于操作依赖于泛型类型参数的条件类型,我们只能期望编译器理解这么多。也许可以让编译器专门检查 T[T[K] extends U ? K : never] 是否可分配给 U。但它会在类型检查器的复杂性和编译时间方面付出一些代价,而且任何好处只会被专门做这种事情的一小部分用户看到。 可能值得,但我不会屏住呼吸。

同时,您有两种通用的方法来处理这个问题。一:明智地使用 type assertion 来告诉编译器它不如你聪明:

const functionOnlyForStrings = <T>(obj: T, key: GetNames<T, string>) => {
    const t = obj[key] as any as string;  // I'm smarter than you, compiler! 
    return t.toUpperCase()
}

二:通过为编译器提供一些它确实正确检查的泛型类型来引导编译器完成情况的类型安全:

const functionOnlyForStrings = <
    T extends Record<K, string>, // constrain T to be a function of K
    K extends GetNames<T, string> // constrain K to be a function of T
>(obj: T, key: K) => {
    const t = obj[key]; // inferred as T[K]
    return t.toUpperCase() // no error
}

之所以可行,是因为编译器已经知道 {[P in K]: V}[K] 可以分配给 V,因此 T[K] 可以分配给 string

希望对您有所帮助;祝你好运!

Link to code