【问题标题】:Using dot notation with functional component in TypeScript在 TypeScript 中使用带有功能组件的点表示法
【发布时间】:2021-07-11 17:50:57
【问题描述】:

ReactJs 官方文档建议在 dot notation 之后创建组件,例如 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”

那么我怎样才能正确输入这个才能使用这个代码结构呢?

【问题讨论】:

    标签: javascript reactjs typescript


    【解决方案1】:

    使用纯 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 导入/导出来实现点符号

    【讨论】:

      【解决方案2】:

      我找到了一种使用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,
      });
      

      【讨论】:

        【解决方案3】:

        在花了很多时间弄清楚如何将点表示法与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 组件之外的任何其他内容时出现错误。这个用例非常具体,但有时可能很有用。

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

        【讨论】:

          【解决方案4】:
          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;
          

          【讨论】:

            猜你喜欢
            • 2020-12-30
            • 2020-08-14
            • 2020-07-07
            • 2017-04-06
            • 2021-10-19
            • 1970-01-01
            • 2014-07-29
            • 2019-09-30
            相关资源
            最近更新 更多