reactjs - Gibt es eine Typisierung für React-Funktionskomponenten, die das Zurückgeben von Fragmenten, null, Strings usw. beinhaltet?
Meine Frage ist eine Klarstellung/Aktualisierung zu dieser stark positiv bewerteten Frage:
Wann verwendet man JSX.Element vs ReactNode vs ReactElement?
Es scheint mir, dass TypeScript nicht gut funktionieren wird, wenn man Funktionskomponenten haben möchte, die Primitive wie String, null usw. zurückgeben.
Zum Beispiel, sagen Sie, Sie haben eine Komponente, die eine Render-Prop möchte:
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>
}
Jetzt kann ich Komponenten erstellen, die diese Render-Prop auf vier verschiedene Weisen erfüllen sollten:
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;
}
Allerdings passt diese FunctionBasedUserPnelThatReturnsAString nicht zu React.ComponentType<{user: User}>;, weil React.FunctionComponent ein React.ReactElement zurückgeben muss, was ein String nicht ist.
React.ClassComponent hingegen hat diese Einschränkung nicht.
const Main = () => {
return <div>
<UserProfilePage RenderUserPanel = {ClassBasedUserPanel}/>
<UserProfilePage RenderUserPanel = {ClassBasedUserPanelThatReturnsString}/>
<UserProfilePage RenderUserPanel = {FunctionBasedUserPanel}/>
{/* ERROR! */}
<UserProfilePage RenderUserPanel = {FunctionBasedUserPanelThatReturnsString}/>
</div>
}
Im Wesentlichen möchte ich einen Typ, der ist:
type TypeImLookingFor<P = {}> = React.ComponentType<P> | (props: P) => ReactNode;
Gibt es das? Gibt es einen Grund, warum es nicht existieren sollte?
Hinweis: Ich benutze derzeit React 16. Vielleicht ist dies etwas, das in React 17 oder 18 behoben wurde.
Antwort #1
Ich werde jetzt meine eigene beste Lösung für React 16 dafür posten - aber ich begrüße andere Antworten, besonders wenn es Updates von React 17/18 gibt.
Das Problem beim Verbreitern des Typs, wie ich es vorgeschlagen habe, ist, dass es tatsächlich nicht funktioniert.
Als Beispiel:
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>;
}
Das heißt, wenn Sie eine Funktionskomponente auf eine 'JSX-artige' Weise verwenden möchten - können Sie keinen String, keine Zahl usw. zurückgeben, auch wenn Sie das bei einer Klassenkomponente tun könnten.
Die beste Lösung ist meiner Meinung nach dann:
- Geben Sie den Rückgabewert Ihrer Funktionskomponenten ausdrücklich als
React.ReactElementan - Wenn sie einen String oder eine Zahl usw. zurückgeben, wickeln Sie ihn in ein Fragment ein
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>;
}
