React - 动态导入组件

41

我有一个页面,根据用户输入呈现不同的组件。目前,我已经硬编码了每个组件的引用,如下所示:

    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

然而,我经常更改/添加组件。有没有办法可能有一个仅存储组件名称和路径的文件,然后在另一个文件中动态导入这些组件?

9个回答

57

我认为可能有些人对我想要实现的内容存在一些困惑。我已经成功解决了我遇到的问题,并在下面展示了我的代码,展示了我是如何解决它的。

独立文件(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

这种方法让我能够非常快速地添加/移除组件,因为所有的导入都在一个文件中,只需要一次更改一行即可。


目标是尽可能少地更改代码来加载新组件。这种方法只需要添加一行即可。 - LEJ
9
你也可以这样做:export { default as Component1 } from './Component1',然后 import * as componentList from './ComponentIndex',最后使用 componentList[type] - totymedli
为什么我们要再次使用.default进行访问? - Siluveru Kiran Kumar
非常感谢,这对我的问题最有效。我正在尝试根据动态配置文件呈现不同的组件。 - Sachin
你会如何在TypeScript中解决这个问题?我遇到了这个错误 => 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],其中 ComponentCar - axel
如果我必须向组件传递大量的属性,该怎么办? - Omar Omeiri

31

由于这个问题实际上很老了,那时的回答可能还可以。但是现在,如果有人遇到同样的问题,应该使用动态导入,以便仅加载所需的组件并避免加载所有不同的组件。

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

在这里尝试示例:演示


9
不错的回答,但值得注意的是,React.lazy目前尚不支持服务器端渲染。 - Luca Fagioli
无法使用完全动态的导入语句:https://webpack.js.org/api/module-methods/#dynamic-expressions-in-import - Jorge Ramirez Zamora

4
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

你可以使用条件渲染代替。希望这能帮到你。 查看此链接

简单方法! 解决了我的问题。我试图在组件使用时加载它。const Modal = lazy(() => import('./sharedmodule/common-modal/common-modal.component'));上面的Modal块会在DOM渲染时立即加载。 因为我这样做了。 返回( <Modal showModal={popup}></Modal> // 这导致它立即呈现 ) - harsh
1
但是仍然会导入组件1、2、3,唯一的变化是你只需隐藏和显示组件。 - PRADEEP GORULE

2
这里是另一种解决方案: 我们获取所需组件的列表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>
        )
    }

当您的应用程序加载时,它会拉取所需的模块。
我考虑使用"promises"来导入它们,但是模块已经是"promises"了。
如果我们晚些时候需要从服务器进行ajax请求,因此我们需要在使用"require"(或类似的东西)捆绑模块之前将其拆分。不确定确切的方法。
"Original Answer"翻译成中文为"最初的回答"。

2
你可以将你的组件打包成微应用,并从url中热加载到你的应用程序中。这是一个支持根据站点级别的配置从路由动态导入组件和微应用的poc。
原始答案:Original Answer 链接:https://github.com/eschall/react-micro-frontend

我看了你的 poc,但由于我从未使用过 redux,所以没有完全理解它的用法。我想从外部 API 导入一个 React 组件。结果可以进行预处理等操作。我只是想知道这是否可能。我在这里提出了这个问题 https://stackoverflow.com/q/59018834/6394630。也许你可以看一下。 - thiloilg

1

还有一种不需要任何 Promise 的动态导入方式:

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将类编译为另一个名称,例如react__WEBPACK_IMPORTED_MODULE_1___default,因此要访问它,我们需要在一个对象中分配编译模块名称,这就是为什么有setClasses,它编译为带有引用的对象。

const setClasses = {
  ColumnSet1Brick: react__WEBPACK_IMPORTED_MODULE_1___default,
  ColumnSet2Brick: react__WEBPACK_IMPORTED_MODULE_2___default,
  ColumnSet3Brick: react__WEBPACK_IMPORTED_MODULE_3___default,
  ColumnSet4Brick: react__WEBPACK_IMPORTED_MODULE_4___default
};

你可以像平常的类名一样导入它:

new (setClasses[`ColumnSet${n}Brick`])(parameters)

0
你可以创建一个组件构建函数,使用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>
    )
}

}


0
如果我们需要使用动态导入,以便仅加载所需的组件并避免加载所有不同的组件。使用代码分割。
(async () => {
  const { Component1 } = await import('./Component1');
})();

-6
你可以使用'react-router-dom'中的Route和Switch根据路径动态渲染组件。这是一个示例:
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 /></>
);  }

1
选择性渲染可以,但不要动态导入 :) - alxpez

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接