Nome componente dinamico React / JSX


168

Sto cercando di eseguire il rendering dinamico dei componenti in base al loro tipo.

Per esempio:

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

Ho provato la soluzione qui proposta per i nomi dei componenti dinamici di React / JSX

Questo mi ha dato un errore durante la compilazione (usando browserify per gulp). Si aspettava XML dove stavo usando una sintassi dell'array.

Potrei risolverlo creando un metodo per ogni componente:

newExampleComponent() {
    return <ExampleComponent />;
}

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

Ciò significherebbe un nuovo metodo per ogni componente che creo. Ci deve essere una soluzione più elegante a questo problema.

Sono molto aperto ai suggerimenti.

Risposte:


158

<MyComponent />compila in React.createElement(MyComponent, {}), che prevede una stringa (tag HTML) o una funzione (ReactClass) come primo parametro.

Potresti semplicemente archiviare la tua classe di componenti in una variabile con un nome che inizia con una lettera maiuscola. Vedi tag HTML e componenti di React .

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

compila a

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

5
I futuri lettori probabilmente troveranno {...this.props}utile anche passare in modo trasparente oggetti di scena ai componenti sottotipati dal genitore. Tiporeturn <MyComponent {...this.props} />
Dr.Strangelove,

4
Assicurati anche di scrivere in maiuscolo il nome della variabile dinamica.
saada,

28
Tieni presente che la tua variabile dovrebbe contenere il componente stesso e non solo il nome del componente come stringa .
totymedli,

3
Se ti stai anche chiedendo perché il var deve essere capitalizzato facebook.github.io/react/docs/…
Nobita

3
Components non è definito
ness-EE,

144

C'è una documentazione ufficiale su come gestire tali situazioni è disponibile qui: https://facebook.github.io/react/docs/jsx-in-depth.html#choosing-the-type-at-runtime

Fondamentalmente dice:

Sbagliato:

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} />;
}

Corretta:

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
COSA MOLTO IMPORTANTE: una variabile maiuscola
mpyw

4
Oltre al fatto che si tratta di documenti ufficiali, è facilmente la risposta migliore e la soluzione più strutturata. Forse è per questo che è nei documenti
:)

1
Grazie per l'ottima risposta. Per i seguenti lettori, si noti che il valore all'interno dell'oggetto mappa (l'oggetto mappa qui è componenti const e il valore è PhotoStory e VideoStory) deve essere senza virgolette - Altrimenti, il componente non verrà visualizzato e verrà visualizzato un errore - nel mio caso l'ho perso e ho perso tempo ...
Erez Lieberman,

11

Dovrebbe esserci un contenitore che associ i nomi dei componenti a tutti i componenti che dovrebbero essere usati in modo dinamico. Le classi di componenti devono essere registrate in un contenitore perché in un ambiente modulare non esiste altrimenti un unico posto in cui sia possibile accedervi. Le classi componenti non possono essere identificate dai loro nomi senza specificarle esplicitamente perché la funzione nameè ridotta al minimo in produzione.

Mappa dei componenti

Può essere un semplice oggetto:

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

O Mapistanza:

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

L'oggetto semplice è più adatto perché beneficia della stenografia della proprietà.

Modulo a botte

Un modulo a botte con esportazioni denominate può fungere da tale mappa:

// 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/>;

Funziona bene con una classe per stile di codice del modulo.

Decoratore

I decoratori possono essere usati con componenti di classe per lo zucchero sintattico, ciò richiede comunque di specificare esplicitamente i nomi delle classi e registrarli in una mappa:

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'
  ...
}

Un decoratore può essere utilizzato come componente di ordine superiore con componenti funzionali:

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

export default dynamic(Bar);

Anche l'uso di proprietà non standarddisplayName anziché casuali è utile per il debug.


Grazie! Quella componenteMappa è pulita e bella :)
Leon Gaban,

10

Ho trovato una nuova soluzione. Nota che sto usando i moduli ES6, quindi sto richiedendo la classe. In alternativa, puoi anche definire una nuova classe React.

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

var type = "example";

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

1
@klinore Hai provato ad accedere defaultall'attributo? vale a dire: require ('./ ExampleComponent'). default?
Khanh Hua,

7

Se i tuoi componenti sono globali, puoi semplicemente fare:

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


1
Funziona magnificamente, specialmente se si utilizza Rails. La risposta accettata non funziona per me, poiché l' Componentsarray non è definito.
Vadim,

3
Invece di collegare oggetti nominati in modo arbitrario all'ambito globale (euw), è necessario considerare la gestione di un registro dei componenti che è possibile registrare e quindi recuperare i riferimenti dei componenti da quando necessario.
slashwhatever

6

Per un componente wrapper, una soluzione semplice sarebbe semplicemente usare React.createElementdirettamente (usando 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 })
    )
  }
}

In realtà ho un componente con lo stesso scopo nel mio progetto)
Dziamid

2

In tutte le opzioni con le mappe dei componenti non ho trovato il modo più semplice per definire la mappa usando la sintassi breve ES6:

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

Avere una mappa non sembra affatto buono con una grande quantità di componenti. In realtà sono sorpreso che nessuno abbia suggerito qualcosa del genere:

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

Questo mi ha davvero aiutato quando avevo bisogno di renderizzare una quantità piuttosto grande di componenti caricati in una forma di array json.


Questo è vicino a ciò che ha funzionato per me e mi ha portato nella giusta direzione. Il tentativo di invocare React.createElement(MyComponent)direttamente ha generato un errore. In particolare, non voglio che il genitore debba conoscere tutti i componenti da importare (in una mappatura), perché sembra un passo in più. Invece, ho usato const MyComponent = require("path/to/component/" + "ComponentNameString").default; return <MyComponent />.
semaj1919

0

Supponiamo di voler accedere a varie viste con il caricamento dinamico dei componenti. Il codice seguente fornisce un esempio pratico di come realizzare ciò utilizzando una stringa analizzata dalla stringa di ricerca di un url.

Supponiamo di voler accedere a una pagina "snozberrys" con due viste uniche utilizzando questi percorsi URL:

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

e

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

definiamo il controller della nostra vista in questo modo:

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

Supponiamo di avere un flag, non diverso dal stateo props:

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

~~~

const Compo = flag ? ComponentOne : ComponentTwo;

~~~

<Compo someProp={someValue} />

Con flag Comporiempire con uno di ComponentOneo ComponentTwoe quindi Compopuò agire come un componente React.


-1

Ho usato un approccio un po 'diverso, poiché conosciamo sempre i nostri componenti attuali, quindi ho pensato di applicare la custodia dell'interruttore. Anche il numero totale di componenti era circa 7-8 nel mio caso.

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} />

    }
  }

L'ho appena trovato di nuovo. Questo è il modo di farlo. Conosci sempre comunque i tuoi componenti e devi caricarli. Quindi questa è un'ottima soluzione. Grazie.
Jake,

-1

Modifica: altre risposte sono migliori, vedi commenti.

Ho risolto lo stesso problema in questo modo:

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

3
Probabilmente non vorrai farlo perché React.createElementverrà invocato per ogni componente nel tuo oggetto di ricerca, anche se solo uno di essi viene visualizzato alla volta. Peggio ancora, inserendo l'oggetto lookup nel rendermetodo del componente genitore, lo farà di nuovo ogni volta che viene eseguito il rendering del genitore. Le risposte migliori sono un modo molto migliore per ottenere la stessa cosa.
Inkling

2
@Inkling, sono d'accordo. Questo era quando stavo appena iniziando con React. Ho scritto questo, poi me ne sono dimenticato tutto quando lo sapevo meglio. Grazie per la segnalazione.
Hammad Akhwand,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.