如何在 TypeScript 中表达与兼容接口协变的 "read only record" 类型?

How to express a "read only record" type in TypeScript that is covariant with compatible interfaces?

我需要定义一个序列化存储对象的方法,但是它的语义要求它只能用于字段键为字符串类型的对象(因为它们必须匹配列的名称,所以它们不能数)。

使用 Record<string, unknown> 类型不适用于此序列化器输入,因为我正在为其提供定义了特定字段的对象(如 interface Foo {foo: number, bar: string},这些不能分配给 Record<string, unknown>).

同样的道理,如果 List 是可变的,我们不能安全地将 List<Cat> 分配给 List<Animal>,但如果它们是不可变的,我们可以。

所以我需要指定泛型类型参数的 keyof Tstring 的子集,或者指定 ReadOnlyRecord<string, unknown> 与任何具有字符串键的接口协变。

建议?

这里不需要考虑方差。 Record<string, unknown> 失败的原因仅仅是因为该实用程序要求其类型参数具有 index signature。出于同样的原因,该方法不能期望获得可分配给 { [x:string]: unknown } 的类型作为其唯一参数。

无论如何,如果没有泛型类型参数,我认为您将无法做到这一点。然后你只需要检查像 { [P in Extract<keyof T, string> : any } 这样的纯字符串键控类型是否可以分配给传入的类型(反过来显然总是通过,因为你的接口是前者的子类型)。

请注意,编译器需要参数中的 X extends T ? T : never 才能从用法中推断出 T,同时仍保持约束。唯一需要注意的是,您将无法捕获 symbol 属性(但如果它们的类型为 unique symbol 则可以):

{
    class Bar {
        public baz<T extends object>(serializable: { [P in Extract<keyof T,string>] : any } extends T ? T : never) { return JSON.stringify(serializable); }
    }

    const foo: Foo = { foo: 42, bar: "answer" };

    const nope = { [1]: true };

    const symb = { [Symbol("ok")]: 24 };

    const US: unique symbol = Symbol("unique");

    const usymb = { [US]: Infinity };

    const bar = new Bar();
    bar.baz(foo); //OK
    bar.baz(nope); //Argument of type '{ 1: boolean; }' is not assignable to parameter of type 'never'
    bar.baz(symb); //OK? Inferred as `{ [x: string]: number; }`
    bar.baz(usymb); //Argument of type '{ [US]: number; }' is not assignable to parameter of type 'never'
}

Playground