React / JSX Dynamic Component Name


165

Ich versuche, Komponenten basierend auf ihrem Typ dynamisch zu rendern.

Beispielsweise:

var type = "Example";
var ComponentName = type + "Component";
return <ComponentName />; 
// Returns <examplecomponent />  instead of <ExampleComponent />

Ich habe die hier vorgeschlagene Lösung ausprobiert. React / JSX-Namen dynamischer Komponenten

Das gab mir einen Fehler beim Kompilieren (mit browserify für gulp). Es erwartete XML, wo ich eine Array-Syntax verwendete.

Ich könnte dies lösen, indem ich für jede Komponente eine Methode erstelle:

newExampleComponent() {
    return <ExampleComponent />;
}

newComponent(type) {
    return this["new" + type + "Component"]();
}

Dies würde jedoch eine neue Methode für jede von mir erstellte Komponente bedeuten. Es muss eine elegantere Lösung für dieses Problem geben.

Ich bin sehr offen für Vorschläge.

Antworten:


157

<MyComponent />Kompiliert nach React.createElement(MyComponent, {}), was eine Zeichenfolge (HTML-Tag) oder eine Funktion (ReactClass) als ersten Parameter erwartet.

Sie können Ihre Komponentenklasse einfach in einer Variablen mit einem Namen speichern, der mit einem Großbuchstaben beginnt. Siehe HTML-Tags vs React Components .

var MyComponent = Components[type + "Component"];
return <MyComponent />;

kompiliert zu

var MyComponent = Components[type + "Component"];
return React.createElement(MyComponent, {});

5
Zukünftige Leser werden es wahrscheinlich auch {...this.props}nützlich finden , Requisiten transparent an untergeordnete Komponenten vom übergeordneten Element zu übergeben. Wiereturn <MyComponent {...this.props} />
Dr.Strangelove

4
Stellen Sie außerdem sicher, dass Sie den Namen Ihrer dynamischen Variablen groß schreiben.
Saada

28
Beachten Sie, dass Ihre Variable die Komponente selbst und nicht nur den Namen der Komponente als Zeichenfolge enthalten sollte .
Totymedli

3
Wenn Sie sich auch fragen, warum die Variable groß geschrieben werden muss facebook.github.io/react/docs/…
Nobita

3
Komponenten ist undefiniert
ness-EE

143

Eine offizielle Dokumentation zum Umgang mit solchen Situationen finden Sie hier: https://facebook.github.io/react/docs/jsx-in-depth.html#choosing-the-type-at-runtime

Grundsätzlich heißt es:

Falsch:

import React from 'react';
import { PhotoStory, VideoStory } from './stories';

const components = {
    photo: PhotoStory,
    video: VideoStory
};

function Story(props) {
    // Wrong! JSX type can't be an expression.
    return <components[props.storyType] story={props.story} />;
}

Richtig:

import React from 'react';
import { PhotoStory, VideoStory } from './stories';

const components = {
    photo: PhotoStory,
    video: VideoStory
};

function Story(props) {
    // Correct! JSX type can be a capitalized variable.
    const SpecificStory = components[props.storyType];
    return <SpecificStory story={props.story} />;
}

25
Sehr wichtige Sache: ein aktivierte Variable
mpyw

4
Abgesehen von der Tatsache, dass es sich um die offiziellen Dokumente handelt, ist es leicht die beste Antwort und die strukturierteste Lösung. Vielleicht ist es deshalb in den Dokumenten
:)

1
Danke für die tolle Antwort. Beachten Sie für die folgenden Leser, dass der Wert innerhalb des Kartenobjekts (das Kartenobjekt hier sind konstante Komponenten und der Wert ist PhotoStory und VideoStory) ohne Anführungszeichen stehen muss. Andernfalls wird die Komponente nicht gerendert und Sie erhalten einen Fehler - in meinem Fall ich habe es verpasst und nur Zeit verschwendet ...
Erez Lieberman

11

Es sollte einen Container geben, der Komponentennamen allen Komponenten zuordnet, die dynamisch verwendet werden sollen. Komponentenklassen sollten in einem Container registriert werden, da es in einer modularen Umgebung ansonsten keinen einzigen Ort gibt, an dem auf sie zugegriffen werden kann. Komponentenklassen können nicht anhand ihrer Namen identifiziert werden, ohne sie explizit anzugeben, da die Funktion namein der Produktion minimiert wird.

Komponentenzuordnung

Es kann ein einfaches Objekt sein:

class Foo extends React.Component { ... }
...
const componentsMap = { Foo, Bar };
...
const componentName = 'Fo' + 'o';
const DynamicComponent = componentsMap[componentName];
<DynamicComponent/>;

Oder MapInstanz:

const componentsMap = new Map([[Foo, Foo], [Bar, Bar]]);
...
const DynamicComponent = componentsMap.get(componentName);

Ein einfaches Objekt ist besser geeignet, da es von der Kurzform des Eigentums profitiert.

Laufmodul

Ein Fassmodul mit benannten Exporten kann als solche Karte fungieren:

// Foo.js
export class Foo extends React.Component { ... }

// dynamic-components.js
export * from './Foo';
export * from './Bar';

// some module that uses dynamic component
import * as componentsMap from './dynamic-components';

const componentName = 'Fo' + 'o';
const DynamicComponent = componentsMap[componentName];
<DynamicComponent/>;

Dies funktioniert gut mit einer Klasse pro Modulcodestil.

Dekorateur

Dekorateure können mit Klassenkomponenten für syntaktischen Zucker verwendet werden. Dazu müssen die Klassennamen noch explizit angegeben und in einer Map registriert werden:

const componentsMap = {};

function dynamic(Component) {
  if (!Component.displayName)
    throw new Error('no name');

  componentsMap[Component.displayName] = Component;

  return Component;
}

...

@dynamic
class Foo extends React.Component {
  static displayName = 'Foo'
  ...
}

Ein Dekorateur kann als Komponente höherer Ordnung mit Funktionskomponenten verwendet werden:

const Bar = props => ...;
Bar.displayName = 'Bar';

export default dynamic(Bar);

Die Verwendung von nicht standardmäßigendisplayName Eigenschaften anstelle von zufälligen Eigenschaften kommt auch dem Debuggen zugute.


Vielen Dank! Diese Komponentenkarte ist sauber und schön :)
Leon Gaban

10

Ich habe eine neue Lösung gefunden. Beachten Sie, dass ich ES6-Module verwende und daher die Klasse benötige. Sie können stattdessen auch eine neue React-Klasse definieren.

var components = {
    example: React.createFactory( require('./ExampleComponent') )
};

var type = "example";

newComponent() {
    return components[type]({ attribute: "value" });
}

1
@klinore Haben Sie versucht, auf das defaultAttribut zuzugreifen ? dh: require ('./ ExampleComponent'). default?
Khanh Hua

7

Wenn Ihre Komponenten global sind, können Sie einfach Folgendes tun:

var nameOfComponent = "SomeComponent";
React.createElement(window[nameOfComponent], {});


1
Dies funktioniert sehr gut, besonders wenn Sie Rails verwenden. Die akzeptierte Antwort funktioniert bei mir nicht, da das ComponentsArray nicht definiert ist.
Vadim

3
Anstatt willkürlich benannte Objekte an den globalen Bereich (euw) anzuhängen, sollten Sie eine Komponentenregistrierung verwalten, die Sie registrieren und bei Bedarf Komponentenreferenzen abrufen können.
Schrägstrich, was auch immer

6

Für eine Wrapper-Komponente besteht eine einfache Lösung darin, sie direkt zu verwenden React.createElement(mit ES6).

import RaisedButton from 'mui/RaisedButton'
import FlatButton from 'mui/FlatButton'
import IconButton from 'mui/IconButton'

class Button extends React.Component {
  render() {
    const { type, ...props } = this.props

    let button = null
    switch (type) {
      case 'flat': button = FlatButton
      break
      case 'icon': button = IconButton
      break
      default: button = RaisedButton
      break
    }

    return (
      React.createElement(button, { ...props, disableTouchRipple: true, disableFocusRipple: true })
    )
  }
}

Ich habe tatsächlich eine Komponente mit dem gleichen Zweck in meinem Projekt)
Dziamid

1

Bei allen Optionen mit Komponentenzuordnungen habe ich nicht den einfachsten Weg gefunden, die Zuordnung mithilfe der kurzen ES6-Syntax zu definieren:

import React from 'react'
import { PhotoStory, VideoStory } from './stories'

const components = {
    PhotoStory,
    VideoStory,
}

function Story(props) {
    //given that props.story contains 'PhotoStory' or 'VideoStory'
    const SpecificStory = components[props.story]
    return <SpecificStory/>
}

1

Eine Karte zu haben, sieht mit einer großen Anzahl von Komponenten überhaupt nicht gut aus. Ich bin tatsächlich überrascht, dass niemand so etwas vorgeschlagen hat:

var componentName = "StringThatContainsComponentName";
const importedComponentModule = require("path/to/component/" + componentName).default;
return React.createElement(importedComponentModule); 

Dieser hat mir wirklich geholfen, als ich eine ziemlich große Anzahl von Komponenten rendern musste, die in Form eines JSON-Arrays geladen wurden.


Dies kommt dem nahe, was für mich funktioniert hat, und hat mich in die richtige Richtung geführt. Der Versuch, React.createElement(MyComponent)direkt aufzurufen, warf einen Fehler auf. Insbesondere möchte ich nicht, dass das übergeordnete Element alle zu importierenden Komponenten (in einem Mapping) kennen muss - da dies ein zusätzlicher Schritt zu sein scheint. Stattdessen habe ich verwendet const MyComponent = require("path/to/component/" + "ComponentNameString").default; return <MyComponent />.
semaj1919

0

Vermutlich möchten wir mit dynamischem Laden von Komponenten auf verschiedene Ansichten zugreifen. Der folgende Code gibt ein funktionierendes Beispiel dafür, wie dies mithilfe einer Zeichenfolge erreicht wird, die aus der Suchzeichenfolge einer URL analysiert wird.

Nehmen wir an, wir möchten über diese URL-Pfade auf eine Seite 'snozberrys' mit zwei eindeutigen Ansichten zugreifen:

'http://localhost:3000/snozberrys?aComponent'

und

'http://localhost:3000/snozberrys?bComponent'

Wir definieren den Controller unserer Ansicht folgendermaßen:

import React, { Component } from 'react';
import ReactDOM from 'react-dom'
import {
  BrowserRouter as Router,
  Route
} from 'react-router-dom'
import AComponent from './AComponent.js';
import CoBComponent sole from './BComponent.js';

const views = {
  aComponent: <AComponent />,
  console: <BComponent />
}

const View = (props) => {
  let name = props.location.search.substr(1);
  let view = views[name];
  if(view == null) throw "View '" + name + "' is undefined";
  return view;
}

class ViewManager extends Component {
  render() {
    return (
      <Router>
        <div>
          <Route path='/' component={View}/>
        </div>
      </Router>
    );
  }
}

export default ViewManager

ReactDOM.render(<ViewManager />, document.getElementById('root'));

0

Angenommen, wir haben eine flag, nicht anders als die stateoder props:

import ComponentOne from './ComponentOne';
import ComponentTwo from './ComponentTwo';

~~~

const Compo = flag ? ComponentOne : ComponentTwo;

~~~

<Compo someProp={someValue} />

Mit Flag Compofüllen Sie mit einem von ComponentOneoder ComponentTwound dann Compokann die Dose wie eine Reaktionskomponente wirken.


-1

Ich habe einen etwas anderen Ansatz verwendet, da wir immer unsere tatsächlichen Komponenten kennen, also dachte ich, Switch Case anzuwenden. Auch die Gesamtzahl der Komponenten lag in meinem Fall bei 7-8.

getSubComponent(name) {
    let customProps = {
       "prop1" :"",
       "prop2":"",
       "prop3":"",
       "prop4":""
    }

    switch (name) {
      case "Component1": return <Component1 {...this.props} {...customProps} />
      case "Component2": return <Component2 {...this.props} {...customProps} />
      case "component3": return <component3 {...this.props} {...customProps} />

    }
  }

Bin gerade wieder darauf gestoßen. Dies ist der Weg, um es zu tun. Sie kennen Ihre Komponenten sowieso immer und müssen sie laden. Das ist also eine großartige Lösung. Vielen Dank.
Jake

-1

Bearbeiten: Andere Antworten sind besser, siehe Kommentare.

Ich habe das gleiche Problem folgendermaßen gelöst:

...
render : function () {
  var componentToRender = 'component1Name';
  var componentLookup = {
    component1Name : (<Component1 />),
    component2Name : (<Component2 />),
    ...
  };
  return (<div>
    {componentLookup[componentToRender]}
  </div>);
}
...

3
Sie möchten dies wahrscheinlich nicht tun, da React.createElementdies für jede Komponente in Ihrem Suchobjekt aufgerufen wird, obwohl jeweils nur eine davon gerendert wird. Schlimmer noch, wenn Sie das Suchobjekt in die renderMethode der übergeordneten Komponente einfügen, wird es jedes Mal erneut ausgeführt, wenn das übergeordnete Objekt gerendert wird. Die besten Antworten sind ein viel besserer Weg, um dasselbe zu erreichen.
Inkling

2
@ Inkling, ich stimme zu. Dies war, als ich gerade mit React anfing. Ich habe das geschrieben und dann alles vergessen, als ich es besser wusste. Vielen Dank für den Hinweis.
Hammad Akhwand
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.