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


168

Я намагаюся динамічно відтворювати компоненти залежно від їх типу.

Наприклад:

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

Я спробував рішення, запропоноване тут, назви динамічних компонентів React / JSX

Це дало мені помилку під час компіляції (використовуючи перегляд для gulp). Він очікував XML там, де я використовував синтаксис масиву.

Я міг би вирішити це, створивши метод для кожного компонента:

newExampleComponent() {
    return <ExampleComponent />;
}

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

Але це означатиме новий метод для кожного створеного нами компонента. Має бути більш елегантне рішення цієї проблеми.

Я дуже відкритий до пропозицій.

Відповіді:


158

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

Ви можете просто зберегти свій компонентний клас у змінній з іменем, що починається з великої літери. Див. Теги HTML проти компонентів React .

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
Майте на увазі, що ваша змінна повинна містити сам компонент , а не лише ім'я компонента як рядок .
totymedli

3
Якщо вам також цікаво, чому варіювати потрібно з великої літери facebook.github.io/react/docs/…
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} />;
}

25
Дуже важлива річ: а капіталізуються змінна
mpyw

4
Окрім того, що це офіційні документи, це найкраща відповідь та найбільш структуроване рішення. Можливо, саме тому в документах
:)

1
Дякую за чудову відповідь. Для наступних читачів зауважте, що значення в межах об’єкта карти (об'єктом map тут є компоненти const, а значення 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атрибута? тобто: вимагати ('./ ExampleComponent'). за замовчуванням?
Хан Хуа

7

Якщо ваші компоненти глобальні, ви можете просто зробити:

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


1
Це прекрасно працює, особливо якщо використовується Рейки. Прийнята відповідь для мене не працює, оскільки 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 })
    )
  }
}

Насправді у моєму проекті є компонент з тією ж метою)
Dziamid

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

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'));

0

Припустимо, у нас є flag, що не відрізняється від stateабо props:

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

~~~

const Compo = flag ? ComponentOne : ComponentTwo;

~~~

<Compo someProp={someValue} />

Якщо прапором Compoзаповнити один із ComponentOneабо, ComponentTwoтоді він Compoможе діяти як Реактивний компонент.


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

    }
  }

Просто натрапив на це ще раз. Це спосіб це зробити. Ви завжди знаєте свої компоненти в будь-якому випадку і вам потрібно їх завантажувати. Тож це чудове рішення. Дякую.
Джейк

-1

Редагувати: інші відповіді краще, дивіться коментарі.

Я вирішив таку ж проблему таким чином:

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

3
Ви, мабуть, не хочете цього робити, тому що React.createElementбуде викликано кожен компонент вашого об’єкта пошуку, хоча одночасно виводиться лише один із них. Гірше, якщо розмістити об’єкт пошуку в renderметоді батьківського компонента, він буде робити це знову щоразу, коли батьківський виведений. Верхні відповіді - це набагато кращий спосіб досягти того самого.
Інклінг

2
@Inkling, я згоден. Це було тоді, коли я тільки починав з React. Я написав це, потім забув про це все, коли знав краще. Дякуємо, що вказали на це.
Хаммад Ахванд
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.