React - динамический импорт компонентов

У меня есть страница, которая отображает различные компоненты на основе пользовательского ввода. На данный момент я жестко запрограммировал импорт для каждого компонента, как показано ниже:

    import React, { Component } from 'react'
    import Component1 from './Component1'
    import Component2 from './Component2'
    import Component3 from './Component3'

    class Main extends Component {
        render() {
            var components = {
                'Component1': Component1,
                'Component2': Component2,
                'Component3': Component3
            };
            var type = 'Component1';  // just an example
            var MyComponent = Components[type];
            return <MyComponent />
        }
    }

    export default Main

Однако я все время меняю / добавляю компоненты. Есть ли способ, возможно, иметь файл, в котором хранятся ТОЛЬКО имена и пути компонентов, которые затем динамически импортируются в другой файл?


person LEJ    schedule 15.01.2018    source источник


Ответы (8)


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

Отдельный файл (ComponentIndex.js):

    let Components = {};

    Components['Component1'] = require('./Component1').default;
    Components['Component2'] = require('./Component2').default;
    Components['Component3'] = require('./Component3').default;

    export default Components

Главный файл (Main.js):

    import React, { Component } from 'react';
    import Components from './ComponentIndex';

    class Main extends Component {
        render () {
            var type = 'Component1'; // example variable - will change from user input
            const ComponentToRender = Components[type];
            return <ComponentToRender/>
        }
    }

    export default Main

Этот метод позволяет мне очень быстро добавлять / удалять компоненты, поскольку импорт выполняется в одном файле и требует изменения только одной строки за раз.

person LEJ    schedule 15.01.2018
comment
Целью было загрузить новые компоненты с минимальными изменениями кода. Этот метод требует добавления только одной строки. - person LEJ; 12.09.2018
comment
Вы также можете сделать export { default as Component1 } from './Component1', затем import * as componentList from './ComponentIndex', затем componentList[type]. - person totymedli; 08.02.2019
comment
почему мы снова обращаемся с .default - person siluveru kiran kumar; 13.12.2019
comment
Большое спасибо, это сработало лучше всего для моей проблемы. Я пытался визуализировать различные компоненты на основе файла конфигурации, который является динамическим. - person Sachin; 03.11.2020
comment
Как бы вы сделали это в машинописном тексте? У меня эта ошибка = ›Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ ComponentCar: () => Element; }'. No index signature with a parameter of type 'string' was found on type '{ ComponentCar: () => Element; }'.ts(7053) это происходит от этой команды const DynamicComponent = Components[Component], где Component это Car - person axel; 19.11.2020
comment
Что делать, если мне нужно передать компоненту множество свойств? - person Omar Omeiri; 15.05.2021

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

const component = React.lazy(() => import('./component.jsx'));

попробуйте пример здесь: демонстрация

person Leo    schedule 24.12.2019
comment
Хороший ответ, но стоит отметить, что React.lazy еще не доступен для рендеринга на стороне сервера. - person Luca Fagioli; 22.03.2020

import React, { Component } from 'react'
import Component1 from './Component1'
import Component2 from './Component2'
import Component3 from './Component3'

class Main extends Component {
    render() {
        var type = 'Component1';  // just an example
        return (
          <div>
            {type == "Component1" && <Component1 />}
            {type == "Component2" && <Component2 />}
            ...
          </div>
        )
    }
}

export default Main

Вы можете использовать условный рендеринг. Надеюсь, это поможет

Проверить это

person user8685433    schedule 15.01.2018
comment
Простой подход! Решил мою проблему. Я пытался загрузить компонент, когда он начинал привыкать. const Modal = lazy (() = ›import ('./ sharedmodule / common-modal / common-modal.component')); Вышеупомянутый модальный фрагмент загружался сразу после рендеринга в DOM. Потому что я делал. return (‹Modal showModal = {popup}› ‹/Modal› // Это сразу вызывало рендеринг) - person harsh; 26.10.2020

Вот еще одно решение: получаем список необходимых компонентов list = ['c1', 'c2', 'c3']. Его можно вытащить из файла json в массив (я использую redux-store, поэтому я инициирую получение форм с помощью this.props.getForms ()). Но вы можете просто создать список компонентов и получить к нему доступ вручную.

    componentDidMount = () => {
//we get elements list from any source to redux-store
        this.props.getForms();
//access redux-store to the list
        const forms = this.props.configBody.sets;
//make deep object copy
        const updatedState = { ...this.state };
        updatedState.modules = [];
        if (forms) {
//here is the very dynamic import magic: we map the import list and prepare to store the imports in Component`s state
            const importPromises = forms.map(p =>
                import(`../TemplateOrders/Template${p.order}`)
                    .then(module => {
                        updatedState.modules.push(module.default)
                    })
                    .catch(errorHandler(p))
            )
//wait till all imports are getting resolved
            Promise.all(importPromises)
                .then(res =>
//then run setState
                    this.setState({ ...updatedState }, () => {
                        console.log(this.state);
                    }))
        }
    }

    render() {
        const forms = this.props.configBody.sets;
//we iterate through the modules and React.createElemet`s 
        const list = this.state.modules
            ? this.state.modules.map((e, i) =>
                createElement(e, { key: forms[i].title }, null)
            )
            : [];
        return (
            <Fragment>
                <Link to='/'>Home</Link>
                <h1>hello there</h1>
//push them all to get rendered as Components
                {list.map(e => e)}
            </Fragment>
        )
    }

Поэтому, когда ваше приложение загружено, оно извлекает необходимые модули.

Я думал использовать обещания для их импорта, но модули уже являются обещаниями.

В случае, если нам нужно использовать их с сервера в последнее время, поэтому нам нужно разделить модули перед объединением с помощью require (или чего-то в этом роде), не знаю точно.

person Alexey Nikonov    schedule 05.06.2019

Вы можете объединить свои компоненты как микроприложения и загрузить их в свое приложение по URL-адресу. Вот документ, который поддерживает динамический импорт компонентов и микроприложений из маршрута на основе конфигурации на уровне сайта.

https://github.com/eschall/react-micro-frontend

person Eric Schall    schedule 03.07.2019
comment
Я посмотрел на ваш poc, но, поскольку я никогда не работал с redux, я не совсем понял его. Я хочу импортировать компонент React из внешнего API. Результат может быть предварительно обработан и т.д. Мне просто интересно, возможно ли это. Я задал этот вопрос здесь, stackoverflow.com/q/59018834/6394630 Может быть, вы можете взглянуть на него - person thiloilg; 25.11.2019

Еще один способ сделать динамический импорт без каких-либо обещаний:

import React from "react";
import ColumnSet1Brick from "./sets/column-set/column-set-1-brick";
import ColumnSet2Brick from "./sets/column-set/column-set-2-brick";
import ColumnSet3Brick from "./sets/column-set/column-set-3-brick";
import ColumnSet4Brick from "./sets/column-set/column-set-4-brick";

const setClasses = {
  ColumnSet1Brick,
  ColumnSet2Brick,
  ColumnSet3Brick,
  ColumnSet4Brick
};

export default class SetBrickStack extends React.Component {

  ...



  getColumnSetInstance = (n) => new (setClasses[`ColumnSet${n}Brick`])(this.paramFactory.getBasicProps());

  getBricksOnInit = () => {
    const columnSets = [1, 2, 3, 4];
    const bricksParams = columnSets.map(this.getColumnSetInstance);
    return bricksParams;
  };
}

Хитрость заключается в том, что babel компилирует классы с другим именем, таким как response__WEBPACK_IMPORTED_MODULE

import React from "react";
import ColumnSet1Brick from "./sets/column-set/column-set-1-brick";
import ColumnSet2Brick from "./sets/column-set/column-set-2-brick";
import ColumnSet3Brick from "./sets/column-set/column-set-3-brick";
import ColumnSet4Brick from "./sets/column-set/column-set-4-brick";

const setClasses = {
  ColumnSet1Brick,
  ColumnSet2Brick,
  ColumnSet3Brick,
  ColumnSet4Brick
};

export default class SetBrickStack extends React.Component {

  ...



  getColumnSetInstance = (n) => new (setClasses[`ColumnSet${n}Brick`])(this.paramFactory.getBasicProps());

  getBricksOnInit = () => {
    const columnSets = [1, 2, 3, 4];
    const bricksParams = columnSets.map(this.getColumnSetInstance);
    return bricksParams;
  };
}
__default, поэтому для доступа к нему нам нужно назначить имя модуля компиляции в одном объекте, поэтому существует setClasses, который компилируется в объект со ссылками

const setClasses = {
  ColumnSet1Brick: react__WEBPACK_IMPORTED_MODULE_1___default,
  ColumnSet2Brick: react__WEBPACK_IMPORTED_MODULE_2___default,
  ColumnSet3Brick: react__WEBPACK_IMPORTED_MODULE
new (setClasses[`ColumnSet${n}Brick`])(parameters)
__default, ColumnSet4Brick: react__WEBPACK_IMPORTED_MODULE_4___default };

и вы можете импортировать его как обычные имена классов:

new (setClasses[`ColumnSet${n}Brick`])(parameters)
person Alexey Nikonov    schedule 23.08.2020

вы можете создать функцию построения компонента, которая использует React.createElement. таким образом вы можете импортировать функцию из вспомогательного файла. трудно показать больше кода в этом примере без дополнительной информации, но вы также можете использовать помощники состояния из этого файла, если ваша цель - полностью удалить логику из этого компонента.

class Main extends Component {

constructor(props) {
  super();
  this.state = { displayComponent: Component1 }
}

buildComponent = () => {
  // create element takes additional params for props and children
  return React.createElement( this.state.displayComponent )
}

render() {
    var type = 'Component1';  // just an example
    return (
      <div>
        { this.buildComponent() }
      </div>
    )
}

}

person Eric Hasselbring    schedule 15.01.2018

Вы можете использовать Route и Switch из response-router-dom для динамического рендеринга компонентов на основе пути. Вот образец

render() {
return (
  <>
    <Header  />
    <BrowserRouter>
      <Switch>
        <Route path="/abc" exact render={() => (<Abc />)}/>
        <Route path="/abcd" exact render={() => (<Abcd {...this.props} />)}/>
        <Route path="/xyz" exact render={() => (<Xyz />)}/>
        
      </Switch>
    </BrowserRouter>
    <Footer /></>
);  }
person Shweta patel    schedule 18.11.2020