如何在 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 T
是 string
的子集,或者指定 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'
}
我需要定义一个序列化存储对象的方法,但是它的语义要求它只能用于字段键为字符串类型的对象(因为它们必须匹配列的名称,所以它们不能数)。
使用 Record<string, unknown>
类型不适用于此序列化器输入,因为我正在为其提供定义了特定字段的对象(如 interface Foo {foo: number, bar: string}
,这些不能分配给 Record<string, unknown>
).
同样的道理,如果 List
是可变的,我们不能安全地将 List<Cat>
分配给 List<Animal>
,但如果它们是不可变的,我们可以。
所以我需要指定泛型类型参数的 keyof T
是 string
的子集,或者指定 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'
}