React / JSX имя динамического компонента

168

Я пытаюсь динамически визуализировать компоненты в зависимости от их типа.

Например:

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

Я попробовал решение, предложенное здесь React / JSX имена динамических компонентов

Это дало мне ошибку при компиляции (используя browserify для gulp). Он ожидал XML, где я использовал синтаксис массива.

Я мог бы решить эту проблему, создав метод для каждого компонента:

newExampleComponent() {
    return <ExampleComponent />;
}

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

Но это будет означать новый метод для каждого компонента, который я создаю. Должно быть более элегантное решение этой проблемы.

Я очень открыт для предложений.

Сэм
источник

Ответы:

158

<MyComponent />компилируется в React.createElement(MyComponent, {}), который ожидает строку (тег HTML) или функцию (ReactClass) в качестве первого параметра.

Вы можете просто сохранить класс вашего компонента в переменной с именем, которое начинается с заглавной буквы. Смотрите HTML-теги против React Components .

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

компилируется в

var MyComponent = Components[type + "Component"];
return React.createElement(MyComponent, {});
Александр Кирзенберг
источник
5
Будущие читатели также, вероятно, найдут {...this.props}полезными прозрачную передачу реквизитов в подтиповые компоненты от родителя. Нравитсяreturn <MyComponent {...this.props} />
Dr.Strangelove
4
Также убедитесь, что вы используете заглавные буквы динамической переменной.
Саада
28
Помните, что ваша переменная должна содержать сам компонент , а не только имя компонента в виде строки .
тотемедли
3
Если вам также интересно, почему
Nobita
3
Компоненты не определены
Ness-EE
144

Официальная документация о том, как справляться с такими ситуациями, доступна здесь: https://facebook.github.io/react/docs/jsx-in-depth.html#choosing-the-type-at-runtime.

В основном это говорит:

Неправильно:

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

Верный:

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
источник
25
ОЧЕНЬ ВАЖНО: переменная с большой буквы
mpyw
4
Помимо того, что это официальные документы, это легко лучший ответ и наиболее структурированное решение. Может быть, поэтому в документах
:)
1
Спасибо за отличный ответ. Для следующих читателей, обратите внимание, что значение в объекте карты (здесь объект карты - это постоянные компоненты, а значение - PhotoStory и VideoStory) должно быть без кавычек - в противном случае компонент не будет отображаться, и вы получите ошибку - в моем Случай, я пропустил это и просто потерял время ...
Эрез Либерман
11

Должен быть контейнер, который отображает имена компонентов на все компоненты, которые должны использоваться динамически. Классы компонентов должны быть зарегистрированы в контейнере, потому что в модульной среде нет единого места, где они могли бы быть доступны. Классы компонентов не могут быть идентифицированы по их именам, не указав их явно, потому что функцияname минимизирована в производстве.

Карта компонентов

Это может быть простой объект:

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

Или Mapэкземпляр:

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

Простой объект является более подходящим, потому что он извлекает выгоду из сокращения свойств.

Ствольный модуль

Модуль ствола с именованным экспортом может действовать как такая карта:

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

Это хорошо работает с одним классом на стиль кода модуля.

декоратор

Декораторы могут использоваться с компонентами класса для синтаксического сахара, для этого все же необходимо явно указать имена классов и зарегистрировать их в карте:

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

Декоратор может использоваться как компонент более высокого порядка с функциональными компонентами:

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

export default dynamic(Bar);

Использование нестандартногоdisplayName вместо случайного свойства также способствует отладке.

Настой Эстус
источник
Спасибо! Этот компонентКарта чиста и хороша :)
Леон Габан,
10

Я разобрался с новым решением. Обратите внимание, что я использую модули ES6, поэтому мне нужен класс. Вместо этого вы также можете определить новый класс React.

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

var type = "example";

newComponent() {
    return components[type]({ attribute: "value" });
}
Сэм
источник
1
@klinore Вы пытались получить доступ к defaultатрибуту? т.е.: require ('./ ExampleComponent'). default?
Хан Хуа
7

Если ваши компоненты являются глобальными, вы можете просто сделать:

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

луч
источник
1
Это прекрасно работает, особенно если вы используете Rails. Принятый ответ не работает для меня, потому что Componentsмассив не определен.
Вадим
3
Вместо того, чтобы прикреплять объекты с произвольными именами к глобальной области (euw), вам следует рассмотреть возможность ведения реестра компонентов, который можно зарегистрировать, а затем извлекать ссылки на компоненты при необходимости.
Слэш, что бы ни было
6

Для компонента-оболочки простым решением было бы просто использовать React.createElementнапрямую (используя 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 })
    )
  }
}
Рикардо Педрони
источник
У меня на самом деле есть компонент с той же целью в моем проекте)
Дзиамид
2

Во всех опциях с картами компонентов я не нашел простейшего способа определения карты с использованием короткого синтаксиса 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
источник
1

Наличие карты не выглядит хорошо с большим количеством компонентов. Я на самом деле удивлен, что никто не предложил что-то вроде этого:

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

Это действительно помогло мне, когда мне нужно было рендерить довольно большое количество компонентов, загруженных в виде массива json.

Артур З.
источник
Это близко к тому, что сработало для меня, и привело меня в правильном направлении. Попытка вызвать React.createElement(MyComponent)напрямую выкинула ошибку. В частности, я не хочу, чтобы родитель должен был знать все компоненты для импорта (в отображении) - потому что это кажется дополнительным шагом. Вместо этого я использовал const MyComponent = require("path/to/component/" + "ComponentNameString").default; return <MyComponent />.
semaj1919
0

Возможно, мы хотим получить доступ к различным представлениям с динамической загрузкой компонента. Следующий код дает рабочий пример того, как этого добиться, используя строку, проанализированную из строки поиска URL-адреса.

Предположим, мы хотим получить доступ к странице «snozberrys» с двумя уникальными представлениями, используя следующие URL-пути:

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

и

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

мы определяем контроллер нашего представления следующим образом:

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
источник
0

Предположим, у нас есть flag, ничем не отличается от stateили props:

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

~~~

const Compo = flag ? ComponentOne : ComponentTwo;

~~~

<Compo someProp={someValue} />

С флагом Compoзаполните одним ComponentOneили, ComponentTwoа затем Compoможет действовать как компонент React.

AmerllicA
источник
-1

Я использовал немного другой подход, так как мы всегда знаем наши фактические компоненты, поэтому я решил применить случай переключения. Кроме того, общее количество компонентов не было около 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} />

    }
  }
abhirathore2006
источник
Просто наткнулся на это снова. Это способ сделать это. Вы всегда знаете свои компоненты, и вам нужно их загрузить. Так что это отличное решение. Спасибо.
Джейк
-1

Изменить: другие ответы лучше, см. Комментарии.

Я решил ту же проблему следующим образом:

...
render : function () {
  var componentToRender = 'component1Name';
  var componentLookup = {
    component1Name : (<Component1 />),
    component2Name : (<Component2 />),
    ...
  };
  return (<div>
    {componentLookup[componentToRender]}
  </div>);
}
...
Хаммад Ахванд
источник
3
Вы, вероятно, не хотите этого делать, потому что React.createElementон будет вызываться для каждого компонента в вашем поисковом объекте, даже если за один раз визуализируется только один из них. Хуже того, помещая объект поиска в renderметод родительского компонента, он будет делать это снова каждый раз, когда будет отображаться родительский компонент. Лучшие ответы - намного лучший способ достижения того же самого.
Inkling
2
@ Inkling, я согласен. Это было, когда я только начинал с React. Я написал это, затем забыл все об этом, когда я знал лучше. Спасибо что подметил это.
Хаммад Ахванд