为了回答您的问题,首先,让我们快速回顾一下不同的“方差”的含义。在下表中,我使用 Microsoft 的定义.NET 文档 https://learn.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance(除了文档中没有的双变量),因为我发现它们最容易掌握:
Variance |
Meaning |
Allowed substitutions |
Bivariance |
Covariance and Contravariance at the same time |
Supertype -> Subtype, Subtype -> Supertype |
Covariance |
Enables you to use a more derived type than originally specified |
Supertype -> Subtype |
Contravariance |
Enables you to use a less derived type than originally specified |
Subtype -> Supertype |
Invariance |
Means that you can use only the type originally specified |
none |
让我们检查一下哪一种类型是超类型,哪一种是子类型:
type T1 = SubOptions extends ParentOptions ? true : false; // false
type T2 = ParentOptions extends SubOptions ? true : false; // true
由此可见,PartentOptions
是一个子类型SubOptions
,而后者是它的超类型。它告诉我们什么?它告诉我们当你注释时subFlow
as Store<SubOptions>
然后尝试分配parentFlow
到它(注释为Store<ParentOptions>
),您正在尝试分配一个需要父类型的子类型.
如果我们参考方差表,我们会发现这需要协方差,但是当您收到错误时,这意味着我们正在处理逆变 or 不变性。现在,当您分配subFlow
to parentFlow
,您正在分配一个需要子类型的超类型.
上面也会导致错误,这意味着这里的赋值实际上是不变的, and @船长约塞连 https://stackoverflow.com/users/8495254/captain-yossarian's comment https://stackoverflow.com/questions/67482793/typescript-narrowing-type-with-generic-error#comment119279210_67482793是正确的:
我相信这是因为 subFlow 和parentFlow 彼此不变。
然而,这种行为是 TypeScript 的设计限制(参见 Anders Hejlsberg 的comment https://github.com/microsoft/TypeScript/issues/32674#issuecomment-716178615在相关问题上)为了健全性牺牲了一些灵活性(删除[keyof Options]
索引,您将看到逆变赋值成为可能)。
As for your solution, due to how variance analysis works, when you move the Params
outwards, the parameter types become covariant (as T[keyof T]
is not aliased here. Note that when reduced to the bare structure, the Param
type is exactly that: type Param<Options> = Options[keyof Options]
, only mapped1).
Take a look at a simplified example0 of your solution:
type Param<Options> = {
[K in keyof Options]: Readonly<{
id: K,
options: Options[K],
}>
}[keyof Options];
interface Store<Options> {
exec: (nextState: Options) => void
}
type SuperOptions = { 'b': { b: number } }
type SubOptions = { 'a': { a: string } } & SuperOptions
const test1 = (subtype: Store<Param<SubOptions>>) => subProcess1(subtype); // OK, Subtype -> Supertype, covariance
const test2 = (supertype: Store<Param<SuperOptions>>) => subProcess2(supertype); // error, Supertype -> Subtype, contravariance
const subProcess1 = (supertype: Store<Param<SuperOptions>>) => supertype.exec({ id: 'b', options: { b: 3 } }); // ok
const subProcess2 = (subtype: Store<Param<SubOptions>>) => subtype.exec({ id: 'b', options: { b: 3 } }); // ok
操场 https://www.typescriptlang.org/play?#code/C4TwDgpgBACghgJzgWwDwHkzAJYHsB2AzgHxQC8UA3gLABQUUA2gNJTb5QDWEIuAZlEw4ChALoAuKACUIcACYEANiFQ16DNnMnMANHQ1RcWPEUlCThFqL3qAvsTq3G3XgPMjRAbjp12wCAh8cADG0ADKwLgIEBjGIqRqBhAAHhDBkgAU+CnAEXD+ZnFEAJTkpABuuNhyjj60oJBQYQCukAjuRORUUADkAEY9kpRQfZL4zch9AVC2M3QN4c19HYRdwz1wg91wkoTACOwA5jMnAGRNrQErdcEiwFD+ewCMXRmESwuSEVEx8EhoLWWRRIxFKZFI7z6MAQuFChEITzeH3AEGKnigAHoMYJdBc+gsoABaUgtNoLHRQW7lRDYOD4UJ0W5Ee6PYAAJle7zJKK+kWiqD+KFQpKuwOIoLKUEh0NhEHhbKR3MgaMx2ICMIQFJFCAJxLx5MpBH2cGpBzpDNojLuUqWMrhCM5lx1PKafN+iCF2pW4rBEKdCwAdCk0hlhtVJP0ehSjMJTN1RlAAMwnWwqrGGThW5k2qEw+0cihI-Eu778wUApbeiXgnOB4PBUOaCMDaPAoYjSTJ2ap9Hp3CcIA
0 Your naming choice slightly adds confusion to an already tough problem: a subtype is called ParentOptions
and supertype SubOptions
, while the relationship between them is the opposite, so I named them SubOptions
and SuperOptions
accordingly to make things clearer.
1 From the discussion in comments, it must be noted that while the relationship between Store<Param<SubOptions>>
and Store<Param<SuperOptions>>
in the solution is covariant, T[keyof T]
here is contravariant (see Anders's comment https://github.com/microsoft/TypeScript/issues/32674#issuecomment-716192565 - the SuperOptions
supertype has fewer properties than the SubOptions
subtype, and there is no discriminant).