【问题标题】:Reason for "K cannot be used to index type" in these cases在这些情况下“K 不能用于索引类型”的原因
【发布时间】:2021-02-16 15:03:47
【问题描述】:

TypeScript 版本:4.1.5(严格模式)

我的问题

我在这里似乎缺少 TypeScript 类型系统的一个元素。

我不确定为什么在以下情况下会出现相应的 TypeScript 错误。我怀疑它们之间存在某种等价关系。

请注意,在 getOption 1、2 和 3 中,TypeScript 如何产生错误。但是,在 getOption 4 和 5 中,没有这样的错误。请任何人帮助我理解为什么在这种情况下,1-3 有错误,但 4-5 没有。

用例

我希望 A 的派生类包含扩展的 Options 属性,使用扩展的 Options 接口。而且我希望基类中的方法始终在调用上下文中推断与当前实例关联的类型。

演示

interface Options {
    test: boolean;
}

class A {
    private options: Options;
    
    constructor(options: Options) {
        this.options = options;
    }
    
    // Setting the 'this' arg doesn't change anything
    getOptions(this: A): Options {
        return this.options;
    }
    
    // Doesn't work - TS2536: Type 'K' cannot be used to index type 'Options'.
    getOption1<O extends Options, K extends keyof O>(name: K): O[K] {
        const options = this.getOptions();
        
        return options[name];
    }

    // Doesn't work - TS2536: Type 'keyof ReturnType' cannot be used to index type 'Options'.
    getOption2(name: keyof ReturnType<this['getOptions']>): ReturnType<this['getOptions']>[keyof ReturnType<this['getOptions']>] {
        const options = this.getOptions();
        
        return options[name];
    }

    // Doesn't work - two of the same error:
    // TS2536: Type 'keyof ReturnType' cannot be used to index type 'Options'.
    getOption3(name: keyof ReturnType<this['getOptions']>): Options[typeof name] {
        const options = this.getOptions();
        
        // TS2536: Type 'keyof ReturnType ' cannot be used to index type 'Options'.
        return options[name];
    }

    // Works
    getOption4(name: keyof Options): Options[typeof name] {
        const options = this.getOptions();

        return options[name];
    }

    // Works
    getOption5(name: keyof Options): ReturnType<this['getOptions']>[typeof name] {
        const options = this.getOptions();

        return options[name];
    }
}

谢谢。

【问题讨论】:

    标签: typescript types


    【解决方案1】:

    问题是选项也可以是子类型,因此与其他选项类型不匹配。

    这行得通:

    interface Options {
        test: boolean;
    }
    
    class A {
        private options: Options;
        
        constructor(options: Options) {
            this.options = options;
        }
        
        // Setting the 'this' arg doesn't change anything
        getOptions(this: A): Options {
            return this.options;
        }
        
        getOption1<O extends Options, K extends keyof O>(name: K): O[K] {
            const options = this.getOptions() as O;
            
            return options[name];
        }
    
        getOption2(name: keyof ReturnType<this['getOptions']>): ReturnType<this['getOptions']>[keyof ReturnType<this['getOptions']>] {
            const options = this.getOptions() as ReturnType<this['getOptions'];
            
            return options[name];
        }
    
        getOption3(name: keyof ReturnType<this['getOptions']>): ReturnType<this['getOptions']>[typeof name] {
            const options = this.getOptions() as ReturnType<this['getOptions']>;
            
            // TS2536: Type 'keyof ReturnType ' cannot be used to index type 'Options'.
            return options[name];
        }
    
        // Works
        getOption4(name: keyof Options): Options[typeof name] {
            const options = this.getOptions();
    
            return options[name];
        }
    
        // Works
        getOption5(name: keyof Options): ReturnType<this['getOptions']>[typeof name] {
            const options = this.getOptions();
    
            return options[name];
        }
    }
    

    但在这种特定情况下,您可能希望为选项创建一个通用类参数:

    class A<TOptions extends Options> {
        private options: TOptions;
        
        constructor(options: TOptions) {
            this.options = options;
        }
        
        getOptions(): TOptions {
            return this.options;
        }
        
        getOption<K extends keyof TOptions>(name: K): TOptions[K] {
            const options = this.getOptions();
            
            return options[name];
        }
    }
    

    【讨论】:

    • 啊——我明白了! this.getOptions() 可以在派生类中被覆盖以返回不同的子类型。我在遇到此问题时使用了您建议的修复程序,但我想也许我遗漏了一些意味着没有必要的东西。
    • 是的,在派生类中,您确实可以返回不同的子类型。在这种情况下,您可能希望使 options 属性通用(请参阅更新的答案)
    • 谢谢!我最初确实这样做了,但这带来了一系列与调用各种函数和方法相关的类似挑战,其中类型参数可以设置为子类型。最后,似乎最不粗糙的方法是“信任”派生类而不是覆盖特定方法。我会再试一次,看看我是否只是严重误解/滥用了 TypeScript 功能,这在这里似乎很可能!
    猜你喜欢
    • 1970-01-01
    • 2014-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-12
    • 2022-10-23
    • 2012-06-20
    • 2017-11-26
    相关资源
    最近更新 更多