尽管我在 React(和 prop-types)方面有很长的经验,但我对 TypeScript 还很陌生。
如果有另一个组件作为道具,我在输入组件时遇到了问题。我已经有一个
Button
组件,并定义了 type
。
现在的问题是,当我创建一个新组件
ButtonGroup
时,它有一个 children
属性,只允许渲染 Button
组件。我将 child
的道具与 Button
类型道具进行比较 - 这意味着我可以传递另一个像 Input
这样的组件,如果我不破坏按钮道具 - 就像拥有 <Input id="input-id" />
就可以了对于打字稿,但我不想允许这样做,因为它是不同类型的组件。
我已经成功地使用了
component.type.displayName
,但打字稿根本不喜欢这个解决方案。
import { ReactElement, Children } from 'react'
type buttonVariantType = 'primary' | 'secondary' | undefined
type buttonProps = {
id?: string
variant?: buttonVariantType
children: ReactNode
...
}
const Button =({ children, id, variant = 'secondary' }) => {
...
return <button ...>{children}</button>
}
interface buttonGroupProps {
children: ReactElement<buttonProps>[]
variant?: buttonVariantType
}
const ButtonGroup = ({ children, variant }: buttonGroupProps) => {
return (
<div>
{Children.map(children, (child) => {
// TS2339: Property 'displayName' does not exist on type 'string | JSXElementConstructor<any>'
if (child?.type.displayName !== 'Button') {
console.log(child.type.displayName, 'not a button')
throw Error('not a button') // ugly workaround, still unacceptable by typescript
}
return (
<Button {...child?.props} variant={variant ?? child.props.variant}>
{child.props.children}
</Button>
)
})}
</div>
)
}
现在,如果我将另一个组件作为
children
放入 ButtonGroup 中,打字稿就可以了,但我不希望这样。
<ButtonGroup variant="danger">
<Input id="123" />
<Button variant="primary" id="primary">
Primary button
</Button>
<Button variant="secondary" id="secondary">
Secondary button
</Button>
</ButtonGroup>
不可能使其编译时安全,原因是,当您使用 jsx 标签声明任何组件时,通过该标签创建的类型包含
any
来表示类型中的值。让我给你举个例子 -
import * as React from "react";
type buttonProps = {
id?: string;
variant?: 'primary' | 'secondary';
children?: React.ReactNode;
}
const div = () => <div></div>;
const input = () => <input></input>;
type Button = React.ReactElement<buttonProps, React.JSXElementConstructor<buttonProps>>;
type Div = ReturnType<typeof div>;
type Input = ReturnType<typeof input>;
type ExpandedButton = { [Property in keyof Button]: Button[Property] }
// ^? type ExpandedButton = { type: React.JSXElementConstructor<buttonProps>; props: buttonProps; key: React.Key | null; }
type ExpandedDiv = { [Property in keyof Div]: Div[Property] };
// ^? type ExpandedDiv = { type: any; props: any; key: React.Key | null; }
type ExpanedInput = { [Property in keyof Input]: Input[Property] };
// ^? type ExpanedInput = { type: any; props: any; key: React.Key | null;
type Assignable = (ExpandedButton extends (ExpandedDiv | ExpanedInput | Div | Input) ? true : false) | ((ExpandedDiv | ExpanedInput | Div | Input) extends ExpandedButton ? true : false);
// ^? type Assignable = true
这表明,任何 jsx 元素都可以分配给另一个元素。至于运行时错误,您几乎是正确的,您应该进行的检查是通过引用相等性检查来查看子组件是否实际上是
Button
组件,像这样 -
import { ReactElement, Children } from 'react'
type buttonVariantType = 'primary' | 'secondary' | undefined
type buttonProps = {
id?: string
variant?: buttonVariantType
children?: React.ReactNode
}
const Button =({ children, id, variant = 'secondary' }: buttonProps) => {
return <button>{children}</button>
}
interface buttonGroupProps {
children: ReactElement<buttonProps>[]
variant?: buttonVariantType
}
const ButtonGroup = ({ children, variant }: buttonGroupProps) => {
return (
<div>
{Children.map(children, (child) => {
if (child?.type !== Button ) {
throw Error('not a button')
}
return (
<Button {...child?.props} variant={variant ?? child.props.variant}>
{child.props.children}
</Button>
)
})}
</div>
)
}