reactjs - Vad betyder felet "JSX-elementtyp '...' har inga konstruktions- eller anropssignaturer"?


Jag skrev lite kod:

function renderGreeting(Elem: React.Component<any, any>) {
    return <span>Hello, <Elem />!</span>;
}

Jag får ett fel:

JSX-elementtypen Elem har inga konstruktions- eller anropssignaturer

Vad betyder det?

Svar #1

Detta är en förväxling mellan konstruktörer och instanser.

Kom ihåg att när du skriver en komponent i React:

class Greeter extends React.Component<any, any> {
    render() {
        return <div>Hello, {this.props.whoToGreet}</div>;
    }
}

Du använder det på detta sätt:

return <Greeter whoToGreet='world' />;

Du använder inte det på det här sättet:

let Greet = new Greeter();
return <Greet whoToGreet='world' />;

I det första exemplet skickar vi runt Greeter, konstruktörsfunktionen för vår komponent. Det är korrekt användning. I det andra exemplet skickar vi runt en instans av Greeter. Det är fel och kommer att misslyckas vid körning med ett fel som "Objekt är inte en funktion".


Problemet med den här koden

function renderGreeting(Elem: React.Component<any, any>) {
    return <span>Hello, <Elem />!</span>;
}

är att det förväntar sig en instans av React.Component. Det du vill ha är en funktion som tar en konstruktor för React.Component:

function renderGreeting(Elem: new() => React.Component<any, any>) {
    return <span>Hello, <Elem />!</span>;
}

eller liknande:

function renderGreeting(Elem: typeof React.Component) {
    return <span>Hello, <Elem />!</span>;
}

Svar #2

Om du vill ta en komponentklass som parameter (istället för en instans), använd React.ComponentClass:

function renderGreeting(Elem: React.ComponentClass<any>) {
    return <span>Hello, <Elem />!</span>;
}

Svar #3

Följande fungerade för mig: https://github.com/microsoft/TypeScript/issues/28631#issuecomment-472606019 Jag fixar det genom att göra något liknande detta:

const Component = (isFoo ? FooComponent : BarComponent) as React.ElementType

Svar #4

Om du använder en Funktionskomponent och skickar en komponent som prop, var lösningen för mig att ändra React.ReactNode till React.ElementType

interface Props{
  GraphComp: React.ElementType
}

const GraphCard:React.FC<Props> = (props) => {
  const { GraphComp } = props;

  return (
   <div> <GraphComp /> </div>
  )
}

Svar #5

När jag konverterar från JSX till TSX och vi behåller vissa bibliotek som js/jsx och konverterar andra till ts/tsx glömmer jag nästan alltid att ändra js/jsx-importsatserna i TSX\TS-filerna från

import * as ComponentName from "ComponentName";

till

import ComponentName from "ComponentName";

Om du anropar en gammal JSX (React.createClass) stilkomponent från TSX, använd då

var ComponentName = require("ComponentName")

Svar #6

Om du skickar en funktionell komponent som props till en annan komponent, använd följande:

import React from 'react';

type RenderGreetingProps = {
  element: React.FunctionComponent<any>
};

function RenderGreeting(props: RenderGreetingProps) {
  const {element: Element} = props;

  return <span>Hello, <Element />!</span>;
}

Svar #7

Om du verkligen inte bryr dig om props så är den bredaste möjliga typen React.ElementType.

Detta skulle tillåta att skicka in inhemska DOM-element som sträng. React.ElementType täcker alla dessa:

renderGreeting('button');
renderGreeting(() => 'Hello, World!');
renderGreeting(class Foo extends React.Component {
   render() {
      return 'Hello, World!'
   }
});

Svar #8

Om du använder material-ui, gå till typen definition av komponenten, som understryks av TypeScript. Mest troligt kommer du att se något liknande detta

export { default } from './ComponentName';

Du har 2 alternativ för att åtgärda felet:

1. Lägg till .default när du använder komponenten i JSX:

import ComponentName from './ComponentName'

const Component = () => <ComponentName.default />

2. Byt namn på komponenten som exporteras som "default" när du importerar:

import { default as ComponentName } from './ComponentName'

const Component = () => <ComponentName />

På detta sätt behöver du inte specificera .default varje gång du använder komponenten.

Svar #9

Det verkar som att det nu finns en speciell ny TypeScript-typ för att möta behovet av denna fråga: JSXElementConstructor. Om du låter någon skicka in konstruktören till ett okänt ReactElement istället för en instans av det ReactElement är detta den korrekta typen att använda.

const renderGreeting = (Elem: JSXElementConstructor<any>) => {
    return <span>Hello, <Elem />!</span>;
}

Detta motsvarar det ovan valda korrekta svaret eftersom: att använda <Elem /> i JSX (dvs. att omsluta en variabel med stor begynnelsebokstav med vinkelparenteser) motsvarar att anropa konstruktorn för ett JSX-element med new-nyckelordet.

Svar #10

Detta är det första resultatet när jag söker efter felet, så jag skulle vilja dela lösningen i mitt specifika fall:

Biblioteket jag använder ser ut så här:

export { default as Arc } from './shapes/Arc';

Jag importerade det felaktigt, vilket orsakar felet:

import Arc from "@visx/shape";

Vad det borde vara

import { Arc } from "@visx/shape";

Svar #11

Som @Jthorpe antydde, tillåter ComponentClass endast antingen Component eller PureComponent men inte en FunctionComponent.

Om du försöker skicka en FunctionComponent kommer TypeScript att ge ett fel som liknar...

Type '(props: myProps) => Element' provides no match for the signature 'new (props: myProps, context?: any): Component<myProps, any, any>'.

Men genom att använda ComponentType istället för ComponentClass tillåter du båda fallen. Enligt React-deklarationsfilen definieras typen som...

type ComponentType<P = {}> = ComponentClass<P, any> | FunctionComponent<P>

Relaterade artiklar