在为实现打字的同时扩展具有通用索引签名的接口

Extending an interface with index signature in generic while having typing for implementations

我有一个系统可以接收 key:object 对,然后让您稍后在应用程序中获取它们。

问题是打开此索引签名会中断键入,因为它允许任何键存在,因此您无法键入 get() 请求。

应用程序和问题的摘要片段如下。

我已经尝试删除这个索引签名,这是一个问题,因为 class Configuration 中的 setting[key].value 不存在。我曾尝试删除基本设置界面,但这基本上会破坏您对输入内容所做的任何更改。我已经尝试了很多辅助函数和守卫来试图强制应用程序意识到它是一个设置。我还没有到必须接受在 get 方法上不输入任何内容的地步。

class Setting {
  constructor(
    public value: number,
    private locked: boolean
  ) { }
}

class Settings {
  [key: string]: Setting; // Comment me out: errors (but typing), leave me in: no errors (no typing)
}

interface SettingsCountryRank extends Settings {
  taiwan: Setting;
  china: Setting;
}

class Configuration<TSettings extends Settings> {
  settings: TSettings;

  public get<U extends keyof TSettings>(key: U): TSettings[U]['value'] {
    return this.settings[key].value;
  }

  constructor(settings: TSettings) {
    this.settings = settings;
  }
}

function main(){
  const test = new Configuration<SettingsCountryRank>({ taiwan: new Setting(1, true), china: new Setting(2, false) });

  test.get('china')
  test.get('notathing');
}

当您在 Settings 中打开和关闭线路时,您可以看到问题。

我认为您应该使用 mapped type whose keys are constrained to known values. I'll be using the Record<K, V> mapped type as defined in the standard library 而不是使用索引签名。

重要的一点是使用 T extends Record<keyof T, Setting> 形式的递归约束,它确保 T 的所有属性必须具有 Setting 类型,但不施加任何约束在键上(索引签名表示将键限制为 "all possible string values",这不是您想要的):

// Settings<T> has the same keys as T, and the values are all Setting
type Settings<T> = Record<keyof T, Setting>;

// constraining SettingsCountryRank to Settings<SettingsCountryRank> is
// makes sure that every declared property has type Setting:
interface SettingsCountryRank extends Settings<SettingsCountryRank> {
    taiwan: Setting;
    china: Setting;
    // oopsie: string; // uncomment this to see an error
}

// constraining TSettings to Settings<TSettings> lets the compiler know that
// all properties of TSettings are of type Setting
class Configuration<TSettings extends Settings<TSettings>> {
    settings: TSettings;

    public get<U extends keyof TSettings>(key: U): TSettings[U]['value'] {
        return this.settings[key].value;
    }

    constructor(settings: TSettings) {
        this.settings = settings;
    }
}

function main() {
    const test = new Configuration<SettingsCountryRank>({ taiwan: new Setting(1, true), china: new Setting(2, false) });
    test.get('china')
    test.get('notathing'); // error as expected
}

我认为这符合您的预期。希望有所帮助;祝你好运!