谁能帮我理解这里发生了什么:TS playground
基本上我有一个 store
有一个 exec
方法,我想缩小子进程的 exec
参数的类型
但是好像store类型是泛型类型有错误
type Param<Options> = {
[K in keyof Options]: Readonly<{
id: K,
options: Options[K],
}>
}[keyof Options];
interface Store<Options> {
exec: (nextState: Param<Options>) => void
}
type ParentOptions = {
'a': { a: string },
} & SubOptions
type SubOptions = {
'b': { b: number },
}
function test(
parentFlowExec: (nextState: Param<ParentOptions>) => void,
subFlowExec: (nextState: Param<SubOptions>) => void,
parentNonGeneric: { exec: (nextState: Param<ParentOptions>) => void },
subNonGeneric: { exec: (nextState: Param<SubOptions>) => void },
parentFlow: Store<ParentOptions>,
subFlow: Store<SubOptions>,
) {
parentFlowExec = subFlowExec; // error: ok
subFlowExec = parentFlowExec; // passed
parentNonGeneric = subNonGeneric; // error: ok
subNonGeneric = parentNonGeneric; // passed
parentFlow = subFlow; // error: ok
subFlow = parentFlow; // error ??
// I plan to use it like this
subProcess(parentFlow);
}
function subProcess(flowStore: Store<SubOptions>) {
flowStore.exec({ id: 'a', options: { a: 'a' } }); // can't call with 'a'
flowStore.exec({ id: 'b', options: { b: 3 } }); // ok
}
更新: 我将参数移出并有 it working 但仍然不明白为什么嵌套它们不起作用
interface Store<Options> {
exec: (nextState: Options) => void
}
// parent2: Store2<Param<ParentOptions>>,
// sub2: Store2<Param<SubOptions>>,
答案 0 :(得分:3)
要回答您的问题,首先,让我们快速回顾一下不同“方差”的含义。在下表中,我使用了 Microsoft .NET documentation 中的定义(文档中没有的二元性除外),因为我发现它们最容易掌握:
差异 | 含义 | 允许替换 |
---|---|---|
二元性 | 同时进行协方差和逆变 | 超类型 -> 子类型,子类型 -> 超类型 |
协方差 | 使您能够使用比最初指定的更派生的类型 | 超类型 -> 子类型 |
逆变 | 使您能够使用比最初指定的更少派生的类型 | 子类型 -> 超类型 |
不变性 | 表示只能使用原来指定的类型 | 无 |
让我们检查您的哪个类型是超类型,哪个是子类型:
type T1 = SubOptions extends ParentOptions ? true : false; // false
type T2 = ParentOptions extends SubOptions ? true : false; // true
因此 PartentOptions
是 SubOptions
的子类型,而后者是其超类型。它告诉我们什么?它告诉我们,当您将 subFlow
注释为 Store<SubOptions>
,然后尝试为其分配 parentFlow
(注释为 Store<ParentOptions>
)时,您正在尝试分配一个 子类型需要超类型的地方。
如果我们参考方差表,我们会看到这需要协方差,但是当你得到一个错误时,这意味着我们正在处理逆变或< em>不变性。现在,当您将 subFlow
分配给 parentFlow
时,您正在分配一个超类型,其中需要一个子类型。
以上也导致了错误,这意味着这里的赋值实际上是不变的,而@captain-yossarian的comment是正确的:
<块引用>我相信这是因为 subFlow 和 parentFlow 彼此不变。
然而,这种行为是 TypeScript 的设计限制(请参阅 Anders Hejlsberg 的 comment 相关问题)牺牲了一些灵活性以保证稳健性(删除 [keyof Options]
索引,您将看到逆变器赋值成为可能)。
至于您的解决方案,由于方差分析的工作原理,当您向外移动 Params
时,参数类型会变成协变(因为 T[keyof T]
在这里没有别名。请注意,当简化为裸结构时,Param
类型正是:type Param<Options> = Options[keyof Options]
,仅映射1)。
看一下您的解决方案的简化示例0:
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
0 你的命名选择稍微增加了一个已经很棘手的问题的混乱:一个子类型被称为 ParentOptions
和超类型 SubOptions
,而它们之间的关系是相反的,所以我相应地将它们命名为 SubOptions
和 SuperOptions
以使事情更清楚。
1 从评论中的讨论来看,必须指出,虽然解决方案中Store<Param<SubOptions>>
和Store<Param<SuperOptions>>
之间的关系是协变, T[keyof T]
这里是逆变(参见 Anders 的 comment - SuperOptions
超类型比 SubOptions
子类型具有更少的属性,并且没有判别式)。