Nom du composant dynamique React / JSX

168

J'essaie de rendre dynamiquement les composants en fonction de leur type.

Par exemple:

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

J'ai essayé la solution proposée ici noms de composants dynamiques React / JSX

Cela m'a donné une erreur lors de la compilation (en utilisant browserify pour gulp). Il attendait du XML là où j'utilisais une syntaxe de tableau.

Je pourrais résoudre cela en créant une méthode pour chaque composant:

newExampleComponent() {
    return <ExampleComponent />;
}

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

Mais cela signifierait une nouvelle méthode pour chaque composant que je crée. Il doit y avoir une solution plus élégante à ce problème.

Je suis très ouvert aux suggestions.

Sam
la source

Réponses:

158

<MyComponent />compile vers React.createElement(MyComponent, {}), qui attend une chaîne (balise HTML) ou une fonction (ReactClass) comme premier paramètre.

Vous pouvez simplement stocker votre classe de composant dans une variable dont le nom commence par une lettre majuscule. Voir les balises HTML et les composants React .

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

compile en

var MyComponent = Components[type + "Component"];
return React.createElement(MyComponent, {});
Alexandre Kirszenberg
la source
5
Les futurs lecteurs trouveront probablement également {...this.props}utile de transmettre de manière transparente les accessoires aux composants sous-typés du parent. Likereturn <MyComponent {...this.props} />
Dr Strangelove
4
Assurez-vous également de mettre en majuscule le nom de votre variable dynamique.
saada
28
Gardez à l'esprit que votre variable doit contenir le composant lui-même et pas seulement le nom du composant sous forme de chaîne .
totymedli
3
Si vous vous demandez aussi pourquoi la var doit être capitalisée facebook.github.io/react/docs/...
Nobita
3
Les composants sont indéfinis
ness-EE
144

Une documentation officielle sur la manière de gérer de telles situations est disponible ici: https://facebook.github.io/react/docs/jsx-in-depth.html#choosing-the-type-at-runtime

En gros, il dit:

Faux:

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

Correct:

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} />;
}
gmfvpereira
la source
25
CHOSE TRÈS IMPORTANTE: une variable capitalisée
mpyw
4
Outre le fait qu'il s'agit de la documentation officielle, c'est facilement la meilleure réponse et la solution la plus structurée. C'est peut-être pour cela que c'est dans la documentation
:)
1
Merci pour une excellente réponse. Pour les lecteurs suivants, notez que la valeur dans l'objet de la carte (l'objet de la carte ici est const components et la valeur est PhotoStory et VideoStory) doit être sans guillemets - Sinon, le composant ne sera pas rendu et vous obtiendrez une erreur - dans mon cas je l'ai manqué et j'ai perdu du temps ...
Erez Lieberman
11

Il devrait y avoir un conteneur qui mappe les noms des composants à tous les composants censés être utilisés de manière dynamique. Les classes de composants doivent être enregistrées dans un conteneur car dans un environnement modulaire, il n'y a pas d'autre endroit unique où ils pourraient être accédés. Les classes de composants ne peuvent pas être identifiées par leurs noms sans les spécifier explicitement car la fonctionname est réduite en production.

Carte des composants

Cela peut être un objet simple:

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

Ou Mapexemple:

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

L'objet simple est plus approprié car il bénéficie de la sténographie de propriété.

Module baril

Un module baril avec des exportations nommées peut agir comme une telle carte:

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

Cela fonctionne bien avec une classe par style de code de module.

Décorateur

Les décorateurs peuvent être utilisés avec des composants de classe pour le sucre syntaxique, cela nécessite toujours de spécifier les noms de classe explicitement et de les enregistrer dans une carte:

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 décorateur peut être utilisé comme composant d'ordre supérieur avec des composants fonctionnels:

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

export default dynamic(Bar);

L'utilisation de propriétés non standarddisplayName au lieu de propriétés aléatoires profite également au débogage.

Fiole d'Estus
la source
Merci! That componentMap est propre et agréable :)
Leon Gaban
10

J'ai trouvé une nouvelle solution. Notez que j'utilise des modules ES6 donc j'exige la classe. Vous pouvez également définir une nouvelle classe React à la place.

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

var type = "example";

newComponent() {
    return components[type]({ attribute: "value" });
}
Sam
la source
1
@klinore Avez-vous essayé d'accéder à l' defaultattribut? ie: require ('./ ExampleComponent'). default?
Khanh Hua
7

Si vos composants sont globaux, vous pouvez simplement faire:

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

Rayon
la source
1
Cela fonctionne à merveille, surtout si vous utilisez des rails. La réponse acceptée ne fonctionne pas pour moi, car le Componentstableau n'est pas défini.
Vadim
3
Plutôt que d'attacher des objets nommés arbitrairement à la portée globale (euw), vous devez envisager de maintenir un registre de composants que vous pouvez enregistrer et récupérer les références de composants si nécessaire.
slashwhatever
6

Pour un composant wrapper, une solution simple serait de simplement utiliser React.createElementdirectement (en utilisant 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 })
    )
  }
}
Ricardo Pedroni
la source
J'ai en fait un composant avec le même but dans mon projet)
Dziamid
2

Dans toutes les options avec les cartes de composants, je n'ai pas trouvé le moyen le plus simple de définir la carte à l'aide de la syntaxe courte 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/>
}
Stalinko
la source
1

Avoir une carte n'a pas du tout l'air bien avec une grande quantité de composants. Je suis en fait surpris que personne n'ait suggéré quelque chose comme ça:

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

Celui-ci m'a vraiment aidé lorsque j'avais besoin de rendre une assez grande quantité de composants chargés sous une forme de tableau json.

Arthur Z.
la source
C'est proche de ce qui a fonctionné pour moi et m'a conduit dans la bonne direction. Essayer d'invoquer React.createElement(MyComponent)directement a jeté une erreur. Plus précisément, je ne veux pas que le parent ait à connaître tous les composants à importer (dans un mappage) - car cela semble être une étape supplémentaire. Au lieu de cela, j'ai utilisé const MyComponent = require("path/to/component/" + "ComponentNameString").default; return <MyComponent />.
semaj1919
0

Suspose nous souhaitons accéder à diverses vues avec chargement de composants dynamiques.Le code suivant donne un exemple de travail de la façon d'accomplir cela en utilisant une chaîne analysée à partir de la chaîne de recherche d'une URL.

Supposons que nous souhaitons accéder à une page `` snozberrys '' avec deux vues uniques en utilisant ces chemins d'URL:

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

et

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

nous définissons le contrôleur de notre vue comme ceci:

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'));
1-14x0r
la source
0

Supposons que nous ayons un flag, pas différent du stateou props:

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

~~~

const Compo = flag ? ComponentOne : ComponentTwo;

~~~

<Compo someProp={someValue} />

Avec le drapeau, Comporemplissez avec l'un des ComponentOneou ComponentTwo, puis le Compopeut agir comme un composant React.

AmerllicA
la source
-1

J'ai utilisé une approche un peu différente, car nous connaissons toujours nos composants réels, j'ai donc pensé appliquer un boîtier de commutation. Le nombre total de composants était également d'environ 7-8 dans mon cas.

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

    }
  }
abhirathore2006
la source
Je viens de tomber dessus à nouveau. C'est la manière de procéder. De toute façon, vous connaissez toujours vos composants et devez les charger. C'est donc une excellente solution. Merci.
Jake
-1

Edit: D'autres réponses sont meilleures, voir les commentaires.

J'ai résolu le même problème de cette façon:

...
render : function () {
  var componentToRender = 'component1Name';
  var componentLookup = {
    component1Name : (<Component1 />),
    component2Name : (<Component2 />),
    ...
  };
  return (<div>
    {componentLookup[componentToRender]}
  </div>);
}
...
Hammad Akhwand
la source
3
Vous ne voudrez probablement pas faire cela, car React.createElementil sera appelé pour chaque composant de votre objet de recherche, même si un seul d'entre eux est rendu à la fois. Pire encore, en mettant l'objet de recherche dans la renderméthode du composant parent, il le refera à chaque fois que le parent est rendu. Les meilleures réponses sont un bien meilleur moyen d'atteindre la même chose.
Inkling
2
@Inkling, je suis d'accord. C'était à ce moment-là que je débutais avec React. J'ai écrit ceci, puis j'ai tout oublié quand je savais mieux. Merci d'avoir fait remarquer cela.
Hammad Akhwand