在TypeScript中对函数组件使用点表示法

d4so4syb  于 2023-05-08  发布在  TypeScript
关注(0)|答案(5)|浏览(149)

官方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>

感谢this question,我知道我可以像在javascript中一样使用函数组件来创建这个结构:

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

Card.Body = Body

export default Card

使用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

问题是现在TypeScript不允许赋值Card.Body = Body,并给予我错误:
类型“FunctionComponent<{}>”上不存在属性“Body”
那么我怎样才能正确地输入它,才能使用这个代码结构呢?

xmjla07d

xmjla07d1#

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

Card.Body = Body;

或者更可读:

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;
fcg9iug3

fcg9iug32#

我发现了一种使用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;

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

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

唯一的缺点是,虽然结果将被类型检查,但对象参数中的各个子组件将不会被类型检查,这可能有助于调试。
一个好的做法是预先定义(和类型检查)参数。

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

export const Table: TableType = Object.assign(TableComponent, tableComposition);

但是由于Object.assign是泛型的,所以这也是有效的:

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

当然,如果你不需要(或者不想)事先显式地指定类型,你也可以这样做,它只会被推断出来。不需要讨厌的黑客。

export const Table = Object.assign(TableComponent, {
    Head: TableHeadComponent,
    Body: TableBodyComponent,
    Row: TableRowComponent,
    Column: TableColumnComponent,
});
2vuwiymt

2vuwiymt3#

在花了很多时间弄清楚如何在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";

卡组件:

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;

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

<Card ref={cardRef}>
    <Card.Body ref={bodyRef}>
        Some random body text
    </Card.Body>
</Card>

🚀奖励功能🚀

如果您需要特定的订单:

...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

!!!如果children不存在,则在尝试添加CardBody组件以外的任何其他组件时,您将收到错误。这个用例非常具体,尽管有时可能很有用。
你当然可以继续添加组件(页眉,页脚,图像等)

zmeyuzjn

zmeyuzjn4#

在这种情况下,如果子组件类型发生变化,使用typeof可以保存一些重构时间:

type CardType = React.FunctionComponent<CardPropsType> & { Body: typeof Body };

const Card: CardType = (props: CardPropsType) => {
  return <>{props.children}<>;
}

Card.Body = Body;

https://www.typescriptlang.org/docs/handbook/2/typeof-types.html

rn0zuynd

rn0zuynd5#

对于纯React函数组件,我是这样做的:

如何使用

import React, {FC} from 'react';
import {Charts, Inputs} from 'components';

const App: FC = () => {

    return (
        <>
            <Inputs.Text/>
            <Inputs.Slider/>

            <Charts.Line/>
        </>
    )
};

export default App;

组件层次结构

|-- src
    |-- components
        |-- Charts
            |-- components
                |-- Bar
                    |-- Bar.tsx
                    |-- index.tsx
                |-- Line
                    |-- Line.tsx
                    |-- index.tsx
        |-- Inputs
            |-- components
                |-- Text
                    |-- Text.tsx
                    |-- index.tsx
                |-- Slider
                    |-- Slider.tsx
                    |-- index.tsx

编码

最后一个组件,如Text.tsx,应该是这样的:

import React, {FC} from 'react';

interface TextProps {
    label: 'string'
}

const Text: FC<TextProps> = ({label}: TextProps) => {

    return (
        <input
            
        />
    )
};

export default Text;

index.tsx
src/components/index.tsx

export {default as Charts} from './Charts';
export {default as Inputs} from './Inputs';

src/components/Inputs/index.tsx

import {Text, Slider} from './components'

const Inputs = {
    Text, 
    Slider
};

export default Inputs;

src/components/Inputs/components/index.tsx

export {default as Text} from './Text';
export {default as Slider} from './Slider';

src/components/Inputs/components/Text/index.tsx

export {default} from './Text';

这就是如何仅使用ES6导入/导出实现点表示法

相关问题