如何在React中基于类型条件强制不同的typescript接口类?



我试图根据组件的类型强制组件的某些属性…基本上,我有一个带有heroStyling类型的主界面:

export type HeroStyling = 'SimpleForm' | 'WithTickerHeader' | 'WithTagLineHeader' | 'WithRating'
export interface AdaptiveHeroMainProps {
heroStyling: HeroStyling
cta: string
ctaUrl: string
image: FluidObject
imageAlt: string
}

,我将根据heroStyling键的类型将其扩展到其他四个类:

export interface AdaptiveHeroMainProps {
heroStyling: HeroStyling
cta: string
ctaUrl: string
image: FluidObject
imageAlt: string
}
export interface WithTagLineHeader extends AdaptiveHeroMainProps {
headingTagLine: string
heading: string
leadParagraph: string
bulletPoints?: string
}
export interface WithTickerHeader extends AdaptiveHeroMainProps {
textCenter: boolean
leadParagraph?: string
bulletPoints?: string
}
export interface SimpleForm extends AdaptiveHeroMainProps {
textCenter: boolean
heading: string
leadParagraph?: string
bulletPoints?: string
}
export interface WithRating extends AdaptiveHeroMainProps {
textCenter: true
reviewStars?: boolean
}

我的问题是我怎么能强制扩展类属性取决于类型heroStyling是什么在我的组件没有TypeScript抱怨一个属性不存在的类型?我需要在渲染时根据它们的true或false值解构所有的道具。

const CampaignHero = ({ 
heroStyling, textCenter, reviewStars, headingTagLine, 
heading, leadParagraph, bulletPoints, cta, ctaUrl, image, imageAlt }
: WithTagLineHeader | WithTickerHeader | SimpleForm | WithRating ): React.ReactElement => {
return (
<section>
{headingTagLine && (
<div      
dangerouslySetInnerHTML={{ __html: headingTagLine }}
/>
)}
{heading && <h1>{heading}</h1>}
{leadParagraph && <p>{leadParagraph}</p>}
{bulletPoints && (
<div
dangerouslySetInnerHTML={{ __html: bulletPoints }}
/>
)}
<Button href={ctaUrl}>{cta}</Button>
{reviewStars && (
<HeroRating />
)}
</div>
<Img alt={imageAlt} fluid={image} loading="eager" />
</section>
)
}

约塞连船长帮了大忙后的问题:如果我像下面这样渲染组件,我不会得到任何错误,但我应该这样做,因为bulletPoints不是withthring接口的一部分。TS只在定义的接口属性中缺少一个时才会抱怨,而不会在有一个不应该存在的属性时抱怨。

<AdaptiveHero
heroStyling={'WithRating'}
reviewStars={true}
textCenter={true}
cta={hero.cta}
ctaUrl={hero.ctaUrl}
image={hero.image.fluid}
imageAlt={hero.image.alt}
bulletPoints={hero.bulletPoints}
/>

如果有联合,则只允许使用在每个联合类型之间共享的公共属性。你不允许使用headingTagLine没有缩小。请参阅文档:

有时你会有一个所有成员都有共同之处的联合。例如,数组和字符串都有一个slice方法。如果联合中的每个成员都有一个公共属性,则可以使用该属性而无需缩小:这是保证类型安全的唯一方法。

为了缩小联合类型,应该创建自定义typeguard,这对运行时有影响。

但是,可以在不影响运行时的情况下处理它。考虑这个例子:

import React, { FC } from 'react'
type FluidObject = {
tag: 'FluidObject'
}
export type HeroStyling = 'SimpleForm' | 'WithTickerHeader' | 'WithTagLineHeader' | 'WithRating'
export interface AdaptiveHeroMainProps {
heroStyling: HeroStyling
cta: string
ctaUrl: string
image: FluidObject
imageAlt: string
}
export interface AdaptiveHeroMainProps {
heroStyling: HeroStyling
cta: string
ctaUrl: string
image: FluidObject
imageAlt: string
}
export interface WithTagLineHeader extends AdaptiveHeroMainProps {
headingTagLine: string
heading: string
leadParagraph: string
bulletPoints?: string
}
export interface WithTickerHeader extends AdaptiveHeroMainProps {
textCenter: boolean
leadParagraph?: string
bulletPoints?: string
}
export interface SimpleForm extends AdaptiveHeroMainProps {
textCenter: boolean
heading: string
leadParagraph?: string
bulletPoints?: string
}

type UnionKeys<T> = T extends T ? keyof T : never;
type StrictUnionHelper<T, TAll> =
T extends any
? T & Partial<Record<Exclude<UnionKeys<TAll>, keyof T>, never>> : never;
// credits goes to https://stackoverflow.com/questions/65805600/type-union-not-checking-for-excess-properties#answer-65805753
type StrictUnion<T> = StrictUnionHelper<T, T>
const Button: FC<{ href: string }> = ({ children }) => <span> children</span>
const CampaignHero = ({
heroStyling, textCenter, reviewStars, headingTagLine,
heading, leadParagraph, bulletPoints, cta, ctaUrl, image, imageAlt }
: StrictUnion<WithTagLineHeader | WithTickerHeader | SimpleForm>): React.ReactElement => {
return (
<section>
{headingTagLine && (
<div
dangerouslySetInnerHTML={{ __html: headingTagLine }}
/>
)}
{heading && <h1>{heading}</h1>}
{leadParagraph && <p>{leadParagraph}</p>}
{bulletPoints && (
<div
dangerouslySetInnerHTML={{ __html: bulletPoints }}
/>
)}
<Button href={ctaUrl}>{cta}</Button>
{reviewStars && (
<HeroRating />
)}
<Img alt={imageAlt} fluid={image} loading="eager" />
</section >
)
}

游乐场

请阅读这个问题/答案,以便了解发生了什么。

你应该知道你的工会不受歧视。为了做到这一点,您应该为每个类型添加唯一属性:

export interface AdaptiveHeroMainProps {
heroStyling: HeroStyling
cta: string
ctaUrl: string
image: FluidObject
imageAlt: string
}
export interface WithTagLineHeader extends AdaptiveHeroMainProps {
heroStyling: 'WithTagLineHeader' // unique
headingTagLine: string
heading: string
leadParagraph: string
bulletPoints?: string
}
export interface WithTickerHeader extends AdaptiveHeroMainProps {
heroStyling: 'WithTickerHeader' // unique
textCenter: boolean
leadParagraph?: string
bulletPoints?: string
}
export interface SimpleForm extends AdaptiveHeroMainProps {
heroStyling: 'SimpleForm' // unique
textCenter: boolean
heading: string
leadParagraph?: string
bulletPoints?: string
}
export interface WithRating extends AdaptiveHeroMainProps {
heroStyling: 'WithRating' // unique
textCenter: true
reviewStars?: boolean
}

完整代码:

import React from 'react'
export type HeroStyling = 'SimpleForm' | 'WithTickerHeader' | 'WithTagLineHeader' | 'WithRating'
type FluidObject = {
tag: 'FluidObject'
}
export interface AdaptiveHeroMainProps {
heroStyling: HeroStyling
cta: string
ctaUrl: string
image: FluidObject
imageAlt: string
}
export interface WithTagLineHeader extends AdaptiveHeroMainProps {
heroStyling: 'WithTagLineHeader'
headingTagLine: string
heading: string
leadParagraph: string
bulletPoints?: string
}
export interface WithTickerHeader extends AdaptiveHeroMainProps {
heroStyling: 'WithTickerHeader'
textCenter: boolean
leadParagraph?: string
bulletPoints?: string
}
export interface SimpleForm extends AdaptiveHeroMainProps {
heroStyling: 'SimpleForm'
textCenter: boolean
heading: string
leadParagraph?: string
bulletPoints?: string
}
export interface WithRating extends AdaptiveHeroMainProps {
heroStyling: 'WithRating'
textCenter: true
reviewStars?: boolean
}
const CampaignHero = (props: WithTagLineHeader | WithTickerHeader | SimpleForm | WithRating): React.ReactElement => {
if(props.heroStyling==='WithRating'){
props.textCenter  // true
}
if(props.heroStyling==='WithTickerHeader'){
props.textCenter // boolean
}
// etc ...
return (
<section>
</section>
)
}
<CampaignHero
heroStyling={'WithRating'}
reviewStars={true}
textCenter={true}
cta={hero.cta}
ctaUrl={hero.ctaUrl}
image={hero.image.fluid}
imageAlt={hero.image.alt}
bulletPoints={hero.bulletPoints} // expected error
/>

游乐场

最新更新