是否有 React Function 组件的类型,包括返回片段、空值、字符串等?
Is there a typing for React Function components that includes returning fragments, nulls, strings etc?
我的问题是 clarification/update 这个高票数问题:
在我看来,当你想要 return 像 string、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
必须 return 一个 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 中修复的问题。
我现在要 post 我自己的 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' 方式使用函数组件 - 您不能 return 字符串、数字等,即使您可以使用 class 组件.
我认为最好的解决方案是:
- 将函数组件的 return 类型明确声明为
React.ReactElement
- 如果他们return输入字符串或数字等,将其包装在片段中
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>;
}
我的问题是 clarification/update 这个高票数问题:
在我看来,当你想要 return 像 string、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
必须 return 一个 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 中修复的问题。
我现在要 post 我自己的 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' 方式使用函数组件 - 您不能 return 字符串、数字等,即使您可以使用 class 组件.
我认为最好的解决方案是:
- 将函数组件的 return 类型明确声明为
React.ReactElement
- 如果他们return输入字符串或数字等,将其包装在片段中
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>;
}