Nombre de componente dinámico React / JSX

168

Estoy tratando de representar dinámicamente componentes en función de su tipo.

Por ejemplo:

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

Probé la solución propuesta aquí React / JSX nombres de componentes dinámicos

Eso me dio un error al compilar (usando browserify para tragar). Esperaba XML donde estaba usando una sintaxis de matriz.

Podría resolver esto creando un método para cada componente:

newExampleComponent() {
    return <ExampleComponent />;
}

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

Pero eso significaría un nuevo método para cada componente que creo. Debe haber una solución más elegante para este problema.

Estoy muy abierto a sugerencias.

Sam
fuente

Respuestas:

157

<MyComponent />compila a React.createElement(MyComponent, {}), que espera una cadena (etiqueta HTML) o una función (ReactClass) como primer parámetro.

Simplemente puede almacenar su clase de componente en una variable con un nombre que comienza con una letra mayúscula. Ver etiquetas HTML frente a React Components .

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

compila a

var MyComponent = Components[type + "Component"];
return React.createElement(MyComponent, {});
Alexandre Kirszenberg
fuente
55
Es probable que los futuros lectores también encuentren {...this.props}útil pasar de manera transparente los accesorios a los componentes subtipados del padre. Me gustareturn <MyComponent {...this.props} />
Dr.Strangelove
44
También asegúrese de capitalizar su nombre de variable dinámica.
saada
28
Tenga en cuenta que su variable debe contener el componente en sí y no solo el nombre del componente como una cadena .
totymedli
3
Si también se pregunta por qué la var necesita ser capitalizada facebook.github.io/react/docs/…
Nobita
3
Componentes indefinidos
ness-EE
144

Hay una documentación oficial sobre cómo manejar tales situaciones disponible aquí: https://facebook.github.io/react/docs/jsx-in-depth.html#choosing-the-type-at-runtime

Básicamente dice:

Incorrecto:

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

Correcto:

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
fuente
25
Cosa
44
Además del hecho de que son los documentos oficiales, es fácilmente la mejor respuesta y la solución más estructurada. Tal vez por eso está en los documentos
:)
1
Gracias por una gran respuesta Para los siguientes lectores, tenga en cuenta que el valor dentro del objeto de mapa (el objeto de mapa aquí es componentes constantes y el valor es PhotoStory y VideoStory) debe estar sin comillas; de lo contrario, el componente no se representará y obtendrá un error en mi caso lo perdí y solo perdí el tiempo ...
Erez Lieberman
11

Debe haber un contenedor que asigne los nombres de los componentes a todos los componentes que se supone que deben usarse dinámicamente. Las clases de componentes deben registrarse en un contenedor porque, en un entorno modular, no hay un lugar único donde se pueda acceder. Las clases de componentes no se pueden identificar por sus nombres sin especificarlas explícitamente porque la función namese minimiza en la producción.

Mapa de componentes

Puede ser objeto simple:

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

O Mapinstancia:

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

El objeto simple es más adecuado porque se beneficia de la taquigrafía de la propiedad.

Módulo de barril

Un módulo de barril con exportaciones con nombre puede actuar como tal mapa:

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

Esto funciona bien con una clase por estilo de código de módulo.

Decorador

Los decoradores se pueden usar con componentes de clase para azúcar sintáctico, esto aún requiere especificar los nombres de clase explícitamente y registrarlos en un mapa:

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

Se puede usar un decorador como componente de orden superior con componentes funcionales:

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

export default dynamic(Bar);

El uso de propiedad no estándar endisplayName lugar de aleatoria también beneficia la depuración.

Estus Flask
fuente
¡Gracias! Ese componenteMapa es limpio y agradable :)
Leon Gaban
10

Descubrí una nueva solución. Tenga en cuenta que estoy usando módulos ES6, así que estoy requiriendo la clase. También podría definir una nueva clase React en su lugar.

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

var type = "example";

newComponent() {
    return components[type]({ attribute: "value" });
}
Sam
fuente
1
@klinore ¿Intentaste acceder al defaultatributo? es decir: require ('./ ExampleComponent'). default?
Khanh Hua
7

Si sus componentes son globales, simplemente puede hacer:

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

Rayo
fuente
1
Esto funciona de maravilla, especialmente si usa Rails. La respuesta aceptada no funciona para mí, porque la Componentsmatriz no está definida.
Vadim
3
En lugar de adjuntar objetos con nombres arbitrarios al ámbito global (euw), debe considerar mantener un registro de componentes que pueda registrar y luego recuperar referencias de componentes cuando sea necesario.
slashwhatever
6

Para un componente de envoltura, una solución simple sería usar React.createElementdirectamente (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 })
    )
  }
}
Ricardo Pedroni
fuente
De hecho, tengo un componente con el mismo propósito en mi proyecto)
Dziamid
2

En todas las opciones con mapas de componentes, no he encontrado la forma más simple de definir el mapa usando la sintaxis corta 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
fuente
1

Tener un mapa no se ve bien en absoluto con una gran cantidad de componentes. De hecho, me sorprende que nadie haya sugerido algo como esto:

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

Este realmente me ayudó cuando necesitaba renderizar una gran cantidad de componentes cargados en forma de matriz json.

Arthur Z.
fuente
Esto está cerca de lo que funcionó para mí y me llevó en la dirección correcta. Intentar invocar React.createElement(MyComponent)directamente arrojó un error. Específicamente, no quiero que el padre tenga que conocer todos los componentes para importar (en un mapeo), porque eso parece un paso adicional. En cambio, yo solía const MyComponent = require("path/to/component/" + "ComponentNameString").default; return <MyComponent />.
semaj1919
0

Supongamos que deseamos acceder a varias vistas con carga dinámica de componentes. El siguiente código ofrece un ejemplo práctico de cómo lograr esto mediante el uso de una cadena analizada a partir de la cadena de búsqueda de una url.

Supongamos que queremos acceder a una página 'snozberrys' con dos vistas únicas utilizando estas rutas de URL:

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

y

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

Definimos el controlador de nuestra vista de esta manera:

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
fuente
0

Supongamos que tenemos un flag, no diferente del stateo props:

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

~~~

const Compo = flag ? ComponentOne : ComponentTwo;

~~~

<Compo someProp={someValue} />

Con la marca de Comporelleno con uno de ComponentOneo ComponentTwoy luego Compopuede actuar como un componente de reacción.

AmerllicA
fuente
-1

Utilicé un enfoque un poco diferente, ya que siempre conocemos nuestros componentes reales, así que pensé en aplicar la caja del interruptor. También el número total de componentes fue de alrededor de 7-8 en mi 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} />

    }
  }
abhirathore2006
fuente
Acabo de encontrar esto de nuevo. Esta es la manera de hacerlo. De todos modos, siempre conoce sus componentes y necesita cargarlos. Entonces esta es una gran solución. Gracias.
Jake
-1

Editar: Otras respuestas son mejores, ver comentarios.

Resolví el mismo problema de esta manera:

...
render : function () {
  var componentToRender = 'component1Name';
  var componentLookup = {
    component1Name : (<Component1 />),
    component2Name : (<Component2 />),
    ...
  };
  return (<div>
    {componentLookup[componentToRender]}
  </div>);
}
...
Hammad Akhwand
fuente
3
Probablemente no desee hacer esto porque React.createElementse invocará para cada componente en su objeto de búsqueda, aunque solo se represente uno de ellos a la vez. Peor aún, al colocar el objeto de búsqueda en el rendermétodo del componente principal, lo volverá a hacer cada vez que se muestre el elemento primario. Las mejores respuestas son una forma mucho mejor de lograr lo mismo.
Inkling
2
@Inkling, estoy de acuerdo. Esto fue cuando recién comenzaba con React. Escribí esto, luego lo olvidé todo cuando supe más. Gracias por señalar eso.
Hammad Akhwand