Nome do componente dinâmico React / JSX

168

Estou tentando renderizar componentes dinamicamente com base em seu tipo.

Por exemplo:

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

Eu tentei a solução proposta aqui Nomes de componentes dinâmicos React / JSX

Isso me deu um erro ao compilar (usando o browserify para gulp). Esperava XML onde eu estava usando uma sintaxe de matriz.

Eu poderia resolver isso criando um método para cada componente:

newExampleComponent() {
    return <ExampleComponent />;
}

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

Mas isso significaria um novo método para cada componente que eu criar. Deve haver uma solução mais elegante para esse problema.

Estou muito aberto a sugestões.

Sam
fonte

Respostas:

157

<MyComponent />compila para React.createElement(MyComponent, {}), que espera uma string (tag HTML) ou uma função (ReactClass) como primeiro parâmetro.

Você pode simplesmente armazenar sua classe de componente em uma variável com um nome que comece com uma letra maiúscula. Veja tags HTML versus componentes de reação .

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

compila para

var MyComponent = Components[type + "Component"];
return React.createElement(MyComponent, {});
Alexandre Kirszenberg
fonte
5
Os futuros leitores também provavelmente acharão {...this.props}útil transmitir de maneira transparente adereços para componentes subtipados do pai. Comoreturn <MyComponent {...this.props} />
Dr.Strangelove 20/16/16
4
Certifique-se também de capitalizar seu nome de variável dinâmica.
Saada
28
Lembre-se de que sua variável deve conter o componente em si e não apenas o nome do componente como uma string .
totymedli
3
Se você também está se perguntando por que o VAR precisa ser capitalizado facebook.github.io/react/docs/...
Nobita
3
Componentes é indefinido
ness-EE
144

Há uma documentação oficial sobre como lidar com essas situações está disponível aqui: https://facebook.github.io/react/docs/jsx-in-depth.html#choosing-the-type-at-runtime

Basicamente diz:

Errado:

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

Corrigir:

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
fonte
25
Coisa muito importante: a capitalizada variável
mpyw
4
Além do fato de serem os documentos oficiais, é facilmente a melhor resposta e a solução mais estruturada. Talvez por isso é no docs:)
domdambrogia
1
Obrigado pela ótima resposta. Para os seguintes leitores, observe que o valor no objeto de mapa (o objeto de mapa aqui é componentes const e o valor é PhotoStory e VideoStory) deve estar sem aspas. Caso contrário, o componente não será renderizado e você receberá um erro - no meu caso eu perdi e apenas perda de tempo ...
Erez Lieberman
11

Deve haver um contêiner que mapeie os nomes dos componentes para todos os componentes que devem ser usados ​​dinamicamente. As classes de componentes devem ser registradas em um contêiner, pois no ambiente modular não há outro local onde elas possam ser acessadas. As classes de componentes não podem ser identificadas por seus nomes sem especificá-las explicitamente porque a função nameé minificada na produção.

Mapa de componentes

Pode ser um objeto simples:

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

Ou Mapexemplo:

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

O objeto simples é mais adequado porque se beneficia da abreviação de propriedades.

Módulo barril

Um módulo barril com exportações nomeadas pode atuar como esse 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/>;

Isso funciona bem com uma classe por estilo de código do módulo.

Decorador

Os decoradores podem ser usados ​​com componentes de classe para açúcar sintático, isso ainda requer especificar nomes de classes explicitamente e registrá-los em um 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'
  ...
}

Um decorador pode ser usado como componente de ordem superior com componentes funcionais:

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

export default dynamic(Bar);

O uso de propriedades não padrão emdisplayName vez de propriedades aleatórias também beneficia a depuração.

Estus Flask
fonte
Obrigado! Esse componentMap é limpo e agradável :) #
314 Leon Gaban
10

Eu descobri uma nova solução. Observe que estou usando os módulos ES6, por isso estou exigindo a classe. Você também pode definir uma nova classe React.

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

var type = "example";

newComponent() {
    return components[type]({ attribute: "value" });
}
Sam
fonte
1
@klinore Você tentou acessar o defaultatributo? ie: require ('./ ExampleComponent'). default?
Khanh Hua
7

Se seus componentes são globais, você pode simplesmente:

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

Raio
fonte
1
Isso funciona lindamente, especialmente se você estiver usando o Rails. A resposta aceita não funciona para mim, porque a Componentsmatriz não está definida.
Vadim
3
Em vez de anexar objetos nomeados arbitrariamente ao escopo global (euw), considere manter um registro de componente no qual possa registrar e recuperar as referências de componente quando necessário.
Slashwhatever 01/11/19
6

Para um componente do wrapper, uma solução simples seria usar React.createElementdiretamente (usando o 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
fonte
Na verdade, tenho um componente com a mesma finalidade no meu projeto)
Dziamid
2

Em todas as opções com mapas de componentes, não encontrei a maneira mais simples de definir o mapa usando a sintaxe curta do 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
fonte
1

Ter um mapa não parece nada bom com uma grande quantidade de componentes. Estou realmente surpreso que ninguém tenha sugerido algo assim:

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

Este realmente me ajudou quando eu precisava renderizar uma quantidade bastante grande de componentes carregados em uma forma de matriz json.

Arthur Z.
fonte
É o que funcionou para mim e me levou na direção certa. Tentar invocar React.createElement(MyComponent)diretamente gerou um erro. Especificamente, não quero que o pai precise conhecer todos os componentes a serem importados (em um mapeamento) - porque isso parece uma etapa extra. Em vez disso, eu usei const MyComponent = require("path/to/component/" + "ComponentNameString").default; return <MyComponent />.
semaj1919 4/06
0

Suponha que desejamos acessar várias visualizações com o carregamento dinâmico de componentes. O código a seguir fornece um exemplo prático de como fazer isso usando uma cadeia de caracteres analisada a partir da cadeia de pesquisa de um URL.

Vamos supor que queremos acessar uma página 'snozberrys' com duas visualizações exclusivas usando estes caminhos de URL:

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

e

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

nós definimos o controlador da nossa view assim:

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

Suponha que temos um flag, não diferente do stateou props:

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

~~~

const Compo = flag ? ComponentOne : ComponentTwo;

~~~

<Compo someProp={someValue} />

Com a bandeira, Compopreencha com um de ComponentOneou ComponentTwoe, em seguida, ele Compopode agir como um componente de reação.

AmerllicA
fonte
-1

Eu usei uma abordagem um pouco diferente, como sempre sabemos nossos componentes reais, então pensei em aplicar o caso do switch. Também o número total de componentes estava em torno de 7-8 no meu 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
fonte
Só me deparei com isso novamente. É assim que se faz. Você sempre conhece seus componentes de qualquer maneira e precisa carregá-los. Portanto, esta é uma ótima solução. Obrigado.
Jake
-1

Editar: Outras respostas são melhores, ver comentários.

Eu resolvi o mesmo problema desta maneira:

...
render : function () {
  var componentToRender = 'component1Name';
  var componentLookup = {
    component1Name : (<Component1 />),
    component2Name : (<Component2 />),
    ...
  };
  return (<div>
    {componentLookup[componentToRender]}
  </div>);
}
...
Hammad Akhwand
fonte
3
Você provavelmente não deseja fazer isso porque React.createElementserá invocado para cada componente em seu objeto de pesquisa, mesmo que apenas um deles esteja sendo renderizado por vez. Pior, ao colocar o objeto de pesquisa no rendermétodo do componente pai, ele fará isso novamente sempre que o pai for renderizado. As principais respostas são uma maneira muito melhor de conseguir a mesma coisa.
quer
2
@ Inkling, eu concordo. Foi quando eu estava apenas começando com o React. Escrevi isso e esqueci tudo quando soube melhor. Obrigado por apontar isso.
precisa saber é o seguinte