首页 > 解决方案 > 是否有用于 React Function 组件的类型,包括返回片段、空值、字符串等?

问题描述

我的问题是对这个高度赞成的问题的澄清/更新:

何时使用 JSX.Element、ReactNode、ReactElement?

在我看来,当您想要拥有返回字符串、null 等原语的函数组件时,TypeScript 不会很好地发挥作用。

例如,假设您有一个需要渲染道具的组件:

import React from "react"; 

type User = {
    id: string; 
    name: string; 
    imgSrc: string; 
}


type UserProfilePageProps = {
    RenderUserPanel: React.ComponentType<{user: User}>; 
}

export const fetchUser = async () : Promise<User> => {
    return {
        id: "123", 
        imgSrc: "placeholdit", 
        name: "Joe Bloggs"
    }
}


const UserProfilePage =  (props: UserProfilePageProps) => {
    const {RenderUserPanel} = props; 
    const [user, setUser] = React.useState<null |User>(null); 

    React.useEffect(()=> {
        fetchUser().then(user => setUser(user)); 
    }, []);

    return <section>
        <h2>User Details</h2>
        {user && <RenderUserPanel user = {user}/>}
    </section> 
}

现在我可以创建组件,这些组件应该以四种不同的方式实现这个渲染道具:

class ClassBasedUserPanel extends React.Component<{user: User}> {
    render() {
        return <div> 
                {this.props.user.name}
        </div>
    }
}

class ClassBasedUserPanelThatReturnsString extends React.Component<{user: User}> {
    render() {
        return  this.props.user.name; 
    }
}

const FunctionBasedUserPanel = (props: {user: User}) => {
    return <div> 
      {props.user.name}
    </div>
}

const FunctionBasedUserPanelThatReturnsString = (props: {user: User}) => {
    return  props.user.name;
}

但是,这FunctionBasedUserPnelThatReturnsAString不合适 React.ComponentType<{user: User}>; ,因为React.FunctionComponent必须返回React.ReactElement一个字符串不是的。

React.ClassComponent另一方面,没有这个约束。

const Main = () => {
    return <div> 
        <UserProfilePage RenderUserPanel = {ClassBasedUserPanel}/>
        <UserProfilePage RenderUserPanel = {ClassBasedUserPanelThatReturnsString}/>
        <UserProfilePage RenderUserPanel = {FunctionBasedUserPanel}/>
        
        {/* ERROR! */}
        <UserProfilePage RenderUserPanel = {FunctionBasedUserPanelThatReturnsString}/>

    </div> 
}

本质上,我想要的是一种类型:


type TypeImLookingFor<P = {}>  = React.ComponentType<P> | (props: P) => ReactNode; 

这存在吗?有没有理由不应该存在?

注意:我目前正在使用 React 16。也许这是在 React 17 或 18 中修复的问题。

标签: reactjstypescriptreact-16

解决方案


我现在将为此发布我自己的 React 16 最佳解决方案 - 但我欢迎其他答案,特别是如果 React 17/18 有更新。

正如我所建议的那样扩大类型的问题在于它实际上不起作用。

举个例子:

type TypeImLookingFor<P = {}>  = React.ComponentType<P> | ((props: P) => React.ReactNode); 

class ComponentA extends React.Component {
    render() {
        return "aaa"; 
    }
}

const ComponentB = () => {
    return "bbb"; 
}

// They do match the type signature
const A: TypeImLookingFor = ComponentA; 
const B: TypeImLookingFor = ComponentB; 

const Main =  () => {
    return <div> 
        <ComponentA/> 
        
        {/* 
        'ComponentB' cannot be used as a JSX component.
        Its return type 'string' is not a valid JSX element.ts(2786) */}
        <ComponentB/>
    </div>;

}

也就是说,如果您想以“JSX-y”方式使用函数组件 - 您不能返回字符串、数字等,即使您可以使用类组件来执行此操作。

在我看来,最好的解决方案是:

  1. 将函数组件的返回类型明确声明为React.ReactElement
  2. 如果他们返回一个字符串或一个数字等,请将其包装在一个片段中
const ComponentC = () : React.ReactElement => {
    //Type 'string' is not assignable to type 'ReactElement<any, string | JSXElementConstructor<any>>'.ts(2322)
    return "aaa"; 
}

const ComponentD = () : React.ReactElement => {
    return <> aaa </>
}

const Main2 =  () => {
    return <div> 
        <ComponentA/> 
        <ComponentD/>
    </div>;

}

推荐阅读