我有两个具有相似 prop 的组件,但有一个关键的区别,一个名为TabsWithState
的组件只包含一个 prop tabs
,它是一个具有以下形状的对象数组:
interface ItemWithState {
name: string;
active: boolean;
}
interface WithStateProps {
tabs: ItemWithState[];
};
另一个称为TabsWithRouter
的类似组件要求项目形状不同:
interface ItemWithRouter {
name: string;
path: string;
}
interface WithRouterProps {
tabs: ItemWithRouter[];
};
我正在尝试创建一个通用组件Tabs
,它将考虑这两种情况。我希望能够编写一个<Tabs />
组件,其中如果传递withRouter
属性,则tabs
属性必须为ItemWithRouter[]
类型。但是如果没有传递withRouter
属性,则它必须为ItemWithState[]
类型。此外,如果传递withRouter
,Tabs
还应接受可选的baseUrl
属性。
我试着创建一个有区别的联合类型,如下所示:
type WithStateProps = {
withRouter?: never;
baseUrl?: never;
tabs: ItemWithState[];
};
type WithRouterProps = {
withRouter: boolean;
baseUrl?: string;
tabs: ItemWithRouter[];
};
type TabsProps = WithStateProps | WithRouterProps;
在我的通用Tabs
组件中,如果withRouter
存在,我想渲染TabsWithRouter
,如果withRouter
不存在,我想渲染TabsWithState
:
const Tabs = (props: TabsProps) => {
const { withRouter } = props;
if (withRouter) {
return <TabsWithRouter {...props} />;
}
return <TabsWithState {...props} />;
};
我最初试图将TabsWithRouter
和TabsWithState
定义为分别接受WithRouterProps
和WithStateProps
的函数组件:
const TabsWithRouter: React.FC<WithRouterProps> = (props: WithRouterProps) => { ... }
const TabsWithState: React.FC<WithStateProps> = (props: WithStateProps) => { ... }
但是我得到了错误Types of property 'withRouter' are incompatible. Type 'undefined' is not assignable to type 'boolean'.
,正如在这个ts操场上看到的
所以我尝试输入TabsWithRouter
和TabsWithState
作为接受TabsProps
作为它们的 prop :
const TabsWithRouter: React.FC<TabsProps> = (props: TabsProps) => {
const { tabs } = props;
console.log(tabs[0].path)
return null
}
const TabsWithState: React.FC<TabsProps> = (props: TabsProps) => {
const { tabs } = props;
console.log(tabs[0].active)
return null
}
但在这种情况下,试图访问tabs[x].path
或tabs[x].active
给我错误Property 'active' does not exist on type 'ItemWithState | ItemWithRouter'. Property 'active' does not exist on type 'ItemWithRouter'
,可以在 * this * ts playground中看到。
有趣的是,在这两种情况下,当我实际尝试 * 使用 * 组件时,这些 prop 都表现得很正确,这可以在ts playground底部的一些示例中看到。
我觉得我已经很接近了,但是我正在努力让这些有区别的联合类型按照我想要的方式运行,这样 typescript 就不会出错了。我在这里读过很多问类似问题的帖子,但是我似乎不能把它们应用到我的场景中出了什么问题。
编辑:
根据请求,下面是我的tsconfig.json:
{
"extends": "./tsconfig.paths.json",
"compilerOptions": {
"baseUrl": "src",
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"types": [
"cypress",
"cypress-file-upload",
"jest"
],
"downlevelIteration": true,
"noFallthroughCasesInSwitch": true
},
"include": [
"src"
]
}
编辑2
这个ts练习场显示了captain-yossarian的解决方案没有强制我希望在Tabs
组件上强制的类型
5条答案
按热度按时间j8yoct9x1#
像这样简单的事情怎么样?根据需要使用
as WithRouterProps
或as WithStateProps
给TypeScript一个提示。ygya80vv2#
别名条件的控制流仅在
ts@4.4
之后可用。因此,使用 destructuredwithRouter
变量在ts@4.4
之前的if
语句中使用时不携带任何类型信息。然后在你的代码中有一个微妙的问题。你的
withRouter
prop的类型为boolean
,如下所示:您正在将其类型缩小为
true
,而不是boolean
(true | false
)。另一个问题是可选类型总是将
undefined
值带入联合体。因此,您必须显式检查这种情况。此检查同时检查显式'undefined'值 * 或 *!('withRouter' in props)
:playground link
由于
ts@4.4
,您可以使用描述的withRouter
作为别名条件:playground check
btxsgosb3#
由于两个并集都有
withROuter
属性,因此TS很难区分它们。我认为联盟值得重构。
UPDATE-添加了重载
现在,TS能够计算出
withState
和withRouter
的位置Playground
顺便说一句,这两个问题可能对你很有意思。TS在与工会合作方面不太擅长重组
m1m5dgzv4#
您可以定义单个
TabsProps
类型:并在接口特定组件中使用它,如:
https://codesandbox.io/s/vigilant-ptolemy-s2r3n
brccelvz5#
在这种情况下,有两种方法可以缩小正确类型的范围
带有附加的
tag
属性沙盒
使用类型 predicate
沙盒
为您的特定情况扩展沙箱