我如何根据 React 中的类型条件强制执行不同的打字稿接口 类?
How can I enforce different typescript interface classes based on a type condition in React?
我正在尝试根据组件的类型强制执行组件的某些属性...基本上我有一个带有 heroStyling 类型的主界面:
export type HeroStyling = 'SimpleForm' | 'WithTickerHeader' | 'WithTagLineHeader' | 'WithRating'
export interface AdaptiveHeroMainProps {
heroStyling: HeroStyling
cta: string
ctaUrl: string
image: FluidObject
imageAlt: string
}
我正在将其扩展到其他四个 classes,具体取决于 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 类型强制执行扩展的 class 属性,而 TypeScript 不会抱怨类型上不存在 属性?我需要在渲染时根据真实值或虚假值解构所有道具。
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>
)
}
captain-yossarian大帮忙后的提问:
如果我如下所示渲染组件,我不会收到任何错误,但我应该这样做,因为 bulletPoints 不是 WithRating 接口的一部分。 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
。
请参阅 docs:
Sometimes you’ll have a union where all the members have something in common. For example, both arrays and strings have a slice method. If every member in a union has a property in common, you can use that property without narrowing:
This is the only way to preserve type safety.
为了缩小联合类型,你应该创建custom 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
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
/>
我正在尝试根据组件的类型强制执行组件的某些属性...基本上我有一个带有 heroStyling 类型的主界面:
export type HeroStyling = 'SimpleForm' | 'WithTickerHeader' | 'WithTagLineHeader' | 'WithRating'
export interface AdaptiveHeroMainProps {
heroStyling: HeroStyling
cta: string
ctaUrl: string
image: FluidObject
imageAlt: string
}
我正在将其扩展到其他四个 classes,具体取决于 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 类型强制执行扩展的 class 属性,而 TypeScript 不会抱怨类型上不存在 属性?我需要在渲染时根据真实值或虚假值解构所有道具。
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>
)
}
captain-yossarian大帮忙后的提问: 如果我如下所示渲染组件,我不会收到任何错误,但我应该这样做,因为 bulletPoints 不是 WithRating 接口的一部分。 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
。
请参阅 docs:
Sometimes you’ll have a union where all the members have something in common. For example, both arrays and strings have a slice method. If every member in a union has a property in common, you can use that property without narrowing: This is the only way to preserve type safety.
为了缩小联合类型,你应该创建custom 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
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
/>