【问题标题】:Enforcing type of key in generic function input在通用函数输入中强制执行键类型
【发布时间】:2018-07-23 18:44:07
【问题描述】:

假设我有通用函数:

function f<T, K extends keyof T>(obj: T, key: K) {
   ...
}

我想强制执行T[K] 的类型,以便我可以执行特定于类型的操作。例如,使用字符串:

function f<T, K extends keyof T>(obj: T, key: K): string {
    return obj[key].toLowerCase();
}

如果不将内容转换为any,这是否可能?

编辑: 为了澄清,我正在寻找基于结果类型禁止某些键的能力。使用上面的示例,类似于:

f({a: 123, b: 'abc'}, 'b') //No errors
f({a: 123, b: 'abc'}, 'a') //Typescript error, T['a'] is not string

【问题讨论】:

    标签: typescript generics


    【解决方案1】:

    要将属性名称限制为仅具有 string 值类型的属性名称,您可以将条件类型与映射类型结合使用:

    type StringProperties<T> = { [K in keyof T]: T[K] extends string ? K : never }[keyof T];
    
    declare function f<T>(obj: T, key: StringProperties<T>): string;
    
    f({a: 123, b: 'abc'}, 'b') // No errors
    f({a: 123, b: 'abc'}, 'a') // Error: Argument of type '"a"' is not assignable to parameter of type '"b"'.
    

    Playground

    【讨论】:

    • 2.5 年后突然出现,感谢您为我节省了很多挫折!
    【解决方案2】:

    您可以通过添加索引类型来强制属性类型,强制所有属性为一种类型:

    interface AllStringProps {
       [key: string]: string;
    }
    
    function f<T extends AllStringProps, K extends keyof T>(obj: T, key: K): string {
        return obj[key].toLowerCase();
    }
    

    但是,这会限制您使用接受任何字符串作为属性的索引类型。

    为了增加一点安全性,您可以将 sn-p 更改为

    type AllStringProps<T> = {
       [key in keyof T]: string;
    }
    
    function f<T extends AllStringProps<T>, K extends keyof T>(obj: T, key: K): string {
        return obj[key].toLowerCase();
    }
    

    这里AllStringProps 类型强制T 中可用的所有键必须是字符串类型。强制所有可能的属性之前的 sn -p 必须是字符串类型。

    在您的用例中,差别不大,但我总是更喜欢尽可能小的约束。

    更新到 cmets 中的一个问题

    比方说,我们只想允许T 的密钥子集。

    我们可以从定义我们想要允许的所有键开始:

    type AllowedKeys = { "s1" , "s2" };
    
    type AllStringProps<AllowedKeys> = {
       [key in keyof AllowedKeys]: string;
    }
    
    function f<T extends AllStringProps<AllowedKeys>, 
               K extends keyof AllowedKeys>(obj: T, key: K): string {
        return obj[key].toLowerCase();
    }
    

    上面的 sn-p 将允许传递以下对象:

    let t1 = {
        s1: "Hello",
        s2: "World",
        other: 4
    }
    

    但不是这个

    let t1 = {
        s1: "Hello",
        s2: 6,
        other: 4
    }
    

    【讨论】:

    • 这将强制 T 具有索引类型。这可以防止像 f({a: 1, b: 'foo'}, 'b') 这样的东西编译。
    • 没错,因为你允许 T 的所有键,你需要强制所有键返回字符串。
    • 您只能强制 T 的一部分键为字符串类型。我将更新答案以包含一个示例
    • 谢谢。我认为我的问题有点不清楚。我想要的是不允许 T 的所有键,只有那些 T[K] 是字符串类型(或任何其他特定类型)的键。我将通过澄清更新问题。
    猜你喜欢
    • 2020-05-12
    • 2014-05-15
    • 1970-01-01
    • 2018-01-19
    • 2014-03-02
    • 2016-08-13
    • 2023-02-02
    • 2018-07-29
    • 2023-03-08
    相关资源
    最近更新 更多