问题描述
interface SlackStageQuery {
stage: 'slack';
}
interface GithubStageQuery {
stage: 'github';
id: string;
}
interface SuccessstageQuery {
stage: 'success';
name: string;
link: string;
}
type StageQuery = SlackStageQuery | GithubStageQuery | SuccessstageQuery;
另外,我有以下界面:
interface Details<S extends OnboardingStage> {
position: number;
title: S extends 'success' ? (name: string) => string : string;
subtitle: string;
button: (query: StageQuery & { stage: S }) => JSX.Element;
}
然后,我有一个符合 ONBOARDING_STAGE_DETAILS
的任意对象 { [p in OnboardingStage]: Details<p> }
。
我还有一个 query
类型的变量 StageQuery
。
要从 button
的子对象之一获取 ONBOARDING_STAGE_DETAILS
属性,我这样做
const foo = ONBOARDING_STAGE_DETAILS[query.stage].button(query)
然而,由于某种原因,我收到了这种类型的错误:
TS2345: Argument of type 'StageQuery' is not assignable to parameter of type 'never'. Type 'SlackStageQuery' is not assignable to type 'never'.
我不知道为什么会这样,因为例如,如果 query.stage === 'slack'
,那么 ONBOARDING_STAGE_DETAILS[query.stage]
将是 Details<'slack'>
类型,其 button
属性采用 { {1}},它正在提供。所以我认为所有类型都可以工作。
有谁知道这是为什么,我该如何解决?谢谢!
解决方法
我无法完全解释类型错误,但我感觉好像是因为query
在编译时是联合类型,那么button(query) {}
方法中参数的推断类型也将是一个联合,不知何故/某处与 { stage : }
的交集无法解析,因此 never
类型已解析。
在这里大声思考...
declare const query: StageQuery;
const details = ONBOARDING_STAGE_DETAILS[query.stage]; // (1)
details.button(query); // (2)
(1): query
是 SlackStageQuery | GithubStageQuery | SuccessStageQuery
类型。
query.stage
是 OnboardingStage
类型,它是 "success" | "slack" | "github"
的联合类型。
所以 details
是 Details<S>
类型,其中 S = OnboardingStage
。
(2):扩展details.button()
的参数类型:
StageQuery & { stage: S }
=> StageQuery & { stage: "success" | "slack" | "github" }
=> (SlackStageQuery | GithubStageQuery | SuccessStageQuery) & { stage: "success" | "slack" | "github" }
=> (SlackStageQuery & { stage: "success" | "slack" | "github" }) | ...
=> /* typescript internal type resolution */
=> never(?!)
如果其他人能弄清楚这如何解析为 never
,或者这是否是调试类型解析的正确方法,我也很乐意学习:)
至于如何修复,您可以利用key remapping重新定义您的界面以完全避免交叉类型:
interface Details<Query extends StageQuery> {
// ^~~~~~~~~~~~~~~~~~note the change here
position: number;
title: Query["stage"] extends 'success' ? (name: string) => string : string;
subtitle: string;
button: (query: Query) => any;
}
type OnboardingStage = StageQuery["stage"];
declare const ONBOARDING_STAGE_DETAILS: {
[query in StageQuery as StageQuery["stage"]]: Details<query>
// ^~~~~ map over the StageQueries,but the object is indexed by StageQuery["stage"]
}
declare const query: StageQuery;
ONBOARDING_STAGE_DETAILS[query.stage].button(query) // OK! :)
const ghdetails: Details<GithubStageQuery> = {
position: 1,title: "",subtitle: "",button: (query) => { /*query inferred as GitHubQuery */}
}
,
由于您已经在使用联合类型 (StageQuery
),因此您不需要还使用交集(每个 StageQuery
类型的变量都已经有一个 stage
属性)所以你可以简单地删除它,代码就可以工作了:
interface Details<S extends OnboardingStage> {
position: number;
title: S extends 'success' ? (name: string) => string : string;
subtitle: string;
button: (query: StageQuery) => any;
}