在 TypeScript 中使用带有功能组件的点表示法

joh*_*pin 9 javascript typescript reactjs

官方 ReactJs 文档建议按照点符号创建组件,如React-bootstrap库:

<Card>
  <Card.Body>
    <Card.Title>Card Title</Card.Title>
    <Card.Text>
      Some quick example text to build on the card title and make up the bulk of
      the card's content.
    </Card.Text>
  </Card.Body>
</Card>
Run Code Online (Sandbox Code Playgroud)

感谢这个问题,我知道我可以使用功能组件来创建这个结构,就像在 javascript 中一样:

const Card = ({ children }) => <>{children}</>
const Body = () => <>Body</>

Card.Body = Body

export default Card
Run Code Online (Sandbox Code Playgroud)

使用 TypeScript,我决定向其中添加相应的类型:

const Card: React.FunctionComponent = ({ children }): JSX.Element => <>{children}</>
const Body: React.FunctionComponent = (): JSX.Element => <>Body</>

Card.Body = Body  // <- Error: Property 'Body' does not exist on type 'FunctionComponent<{}>'

export default Card
Run Code Online (Sandbox Code Playgroud)

现在的问题是 TypeScript 不允许分配Card.Body = Body并给我错误:

类型“FunctionComponent<{}>”上不存在属性“Body”

那么如何正确键入以使用此代码结构呢?

rit*_*taj 13

const Card: React.FunctionComponent & { Body: React.FunctionComponent } = ({ children }): JSX.Element => <>{children}</>
const Body: React.FunctionComponent = (): JSX.Element => <>Body</>

Card.Body = Body;
Run Code Online (Sandbox Code Playgroud)

或更易读:

type BodyComponent = React.FunctionComponent;
type CardComponent = React.FunctionComponent & { Body: BodyComponent };

const Card: CardComponent = ({ children }): JSX.Element => <>{children}</>;
const Body: BodyComponent = (): JSX.Element => <>Body</>;

Card.Body = Body;
Run Code Online (Sandbox Code Playgroud)


Fil*_*ský 7

我找到了一种巧妙的方法Object.assign来使点表示法与 ts 一起使用。有类似的用例

type TableCompositionType = {
    Head: TableHeadComponentType;
    Body: TableBodyComponentType;
    Row: TableRowComponentType;
    Column: TableColumnComponentType;
};
type TableType = TableComponentType & TableCompositionType;


export const Table: TableType = TableComponent;
Table.Head = TableHeadComponent;
Table.Body = TableBodyComponent;
Table.Row = TableRowComponent;
Table.Column = TableColumnComponent;
Run Code Online (Sandbox Code Playgroud)

其中 ts 会抛出错误。我的基本工作解决方案是:

export const Table: TableType = Object.assign(TableComponent, {
    Head: TableHeadComponent,
    Body: TableBodyComponent,
    Row: TableRowComponent,
    Column: TableColumnComponent,
});
Run Code Online (Sandbox Code Playgroud)

唯一的缺点是,虽然结果将被类型检查,但对象参数内的各个子组件不会被检查,这可能有助于调试。

一个好的做法是预先定义(和类型检查)参数。

const tableComposition: TableCompositionType = {
    Head: TableHeadComponent,
    Body: TableBodyComponent,
    Row: TableRowComponent,
    Column: TableColumnComponent,
};

export const Table: TableType = Object.assign(TableComponent, tableComposition);
Run Code Online (Sandbox Code Playgroud)

但由于Object.assign是通用的,因此这也是有效的:

export const Table = Object.assign<TableComponentType, TableCompositionType>(TableComponent, {
    Head: TableHeadComponent,
    Body: TableBodyComponent,
    Row: TableRowComponent,
    Column: TableColumnComponent,
});
Run Code Online (Sandbox Code Playgroud)

当然,如果您不需要(或不想)事先显式指定类型,您也可以这样做,并且只会推断出类型。不需要令人讨厌的黑客攻击。

export const Table = Object.assign(TableComponent, {
    Head: TableHeadComponent,
    Body: TableBodyComponent,
    Row: TableRowComponent,
    Column: TableColumnComponent,
});
Run Code Online (Sandbox Code Playgroud)


dod*_*rat 5

在花了很多时间弄清楚如何在forwardRef组件中使用点表示法之后,这是我的实现:

卡体组件:

export const CardBody = forwardRef<HTMLDivElement, CardBodyProps>(({ children, ...rest }, ref) => (
    <div {...rest} ref={ref}>
        {children}
    </div>
));

//Not necessary if Bonus feature wont be implemented 
CardBody.displayName = "CardBody";
Run Code Online (Sandbox Code Playgroud)

卡组件:

interface CardComponent extends React.ForwardRefExoticComponent<CardProps & React.RefAttributes<HTMLDivElement>> {
    Body: React.ForwardRefExoticComponent<CardBodyProps & React.RefAttributes<HTMLDivElement>>;
}

const Card = forwardRef<HTMLDivElement, CardProps>(({ children, ...rest }, ref) => (
    <div {...rest} ref={ref}>
        {children}
    </div>
)) as CardComponent;

Card.Body = CardBody;

export default Card;
Run Code Online (Sandbox Code Playgroud)

在你的代码中使用它看起来像这样:

<Card ref={cardRef}>
    <Card.Body ref={bodyRef}>
        Some random body text
    </Card.Body>
</Card>
Run Code Online (Sandbox Code Playgroud)

奖励功能

如果您想要特定订单:

...CardComponentInterface

const Card = forwardRef<HTMLDivElement, CardProps>(({ children, ...rest }, ref) => {

    const body: JSX.Element[] = React.Children.map(children, (child: JSX.Element) =>
        child.type?.displayName === "CardBody" ? child : null
    );

    return(
        <div {...rest} ref={ref}>
           {body}
        </div>
    )
}) as CardComponent;

...Export CardComponent
Run Code Online (Sandbox Code Playgroud)

!!! 如果children不存在,则在尝试添加除 CardBody 组件之外的其他任何内容时将会收到错误消息。这个用例非常具体,尽管有时可能很有用。

您当然可以继续添加组件(页眉、页脚、图像等)