【问题标题】:Dynamic loading of react componentsReact 组件的动态加载
【发布时间】:2023-03-22 16:25:01
【问题描述】:

我需要动态加载一个反应组件。

我从用户那里得到要作为字符串加载的组件的名称。我正在使用 webpack

如何动态加载组件而不是使用静态导入语句。似乎Require.Ensure 不评估表达式。我想要实现的是这样的。

require.ensure([ "./widgets/" + componentName ] ,(require) => {
    let Component = require("./widgets/" + componentName);   
});

但这似乎不起作用。

【问题讨论】:

  • require.ensure 是静态分析的,正确的。你会找到另一种方式。
  • 可以使用 webpack 吗?
  • 您可以设置require.context,然后设置require
  • 不会 require.context 从目录中创建一个巨大的包吗?我真的很感激一些示例代码。谢谢:)
  • 如果require.context 不删减,你会反对使用little-loader 之类的动态加载器吗?你仍然需要编译你的组件,但它可以在一个完全动态的情况下工作。

标签: reactjs webpack


【解决方案1】:

基本上,它归结为预先创建您将需要的所有块。然后你只需要一种动态引用它们的方法。这是我基于我的解决方案:

http://henleyedition.com/implicit-code-splitting-with-react-router-and-webpack

这是我做的,因为我不使用 React Router(旁注:我觉得它不适合 redux 或动画):

//loader:
{
  test: (folder)\/.*\.js,
  include: path.resolve(__dirname, 'src')
  loader: ['lazy?bundle', 'babel']
}

//dynamic usage within React component:
const My_COMPONENTS = {
   ComponentA: require('./folder/ComponentA'),
   ComponentB: require('./folder/ComponentB'),
}

class ParentComponent extends React.Component {
    componentDidMount() {
        My_COMPONENTS[this.props.name](component => this.setState({component}));
    } 
    render() {
       return <this.state.component />;
    }
}

所以结果是您正在动态渲染一个组件,但从一组静态的预先确定的可能性中 - 同时,只向客户端发送比访问者实际感兴趣的块更多的内容。

另外,这是我的一个组件,它做得很好:

import React from 'react';
import Modal from './widgets/Modal';

export default class AjaxModal extends React.Component {
  constructor(props, context) {
    super(props, context);
    this.state = {
      Content: null
    };
  }

  componentDidMount() {
    if(this.props.show) {
      this.loadContent();
    }
  }

  componentWillReceiveProps({show}) {
    if(show && !this.state.Content) {
      this.loadContent(1200); //dont interfere with animation
    }
  }

  loadContent(ms=0) {
    setTimeout(() => {
      this.props.requestLazyBundle(({default: Content}) => {
        this.setState({Content});
      });
    }, ms);
  }

  render() {
    let {Content} = this.state;

    return (
      <Modal title={this.props.title} {...this.props} loading={!Content}>
        {Content ? <Content /> : null}
      </Modal>
    );
  }
}

this.props.requestLazybundle 的形式传递异步需求捆绑器函数,如下所示:

render() {

  let requestLazyBundle = require('bundle?lazy&name=AnotherComponent!../content/AnotherComponent');

  return (
    <AjaxModal title='Component Name' {...props} requestLazyBundle={requestLazyBundle} />
  );
}

【讨论】:

【解决方案2】:

请查看我为完整源代码提供的这个要点页面 https://gist.github.com/SamanShafigh/a0fbc2483e75dc4d6f82ca534a6174d4

假设您有 4 个组件,分别称为 D1、D2、D3。您需要的是创建依赖注入和依赖容器机制。这是一个非常简单的实现

假设你有一个像这样定义你的组件的配置文件

export default [
  {
    name:'D1',
    path:'D1'
  },
  {
    name:'D2',
    path:'D2'
  },
  {
    name:'D3',
    path:'D3'
}];

然后你可以有一个像这样的组件容器

import componentsConfig from 'ComponentsConfig';

let components = {};

for (var i = 0; i < componentsConfig.length; i++) {
  let componentConfig = componentsConfig[i];
  // Check if component is not already loaded then load it
  if (components[componentConfig.name] === undefined) {
    components[componentConfig.name] = require(`${componentConfig.path}`).default;
  }
}

export default components;

最后在你想加载组件的地方你可以使用你的组件容器来动态加载你的组件,或者换句话说你可以注入你的组件

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

class App extends Component {
  render() {
    let components = ['D1', 'D2', 'D3'];

    return (
      <div>
        <h2>Dynamic Components Loading</h2>
        {components.map((componentId) => {
          let Component = ComponentContainer[componentId];
          return <Component>{componentId}</Component>;
        })}
      </div>
    );
  }
}

export default App;

【讨论】:

  • 我尝试了同样的方法,它不起作用,错误:找不到模块“。”有没有工作回购?因为我正在尝试所有可能的方式,但仍然得到相同的结果。
  • @LionelDcosta 我已经提供了代码gist.github.com/SamanShafigh/a0fbc2483e75dc4d6f82ca534a6174d4 但是请做更多的研究来找出这种方法的缺点,我不确定它是否适合大型项目
【解决方案3】:
import React, { Component } from "react";

class DynamicLoader extends Component {

  render() {
    const COMPONENTS = {
      CompA: require('./CompA'),
      CompB: require('./CompB'),
    }
    if(this.props.component && COMPONENTS[this.props.component]) {
      let Component = COMPONENTS[this.props.component].default;
      return <Component />
    }
  }
}

export default DynamicLoader;
<DynamicLoader component="CompA" />

【讨论】:

    【解决方案4】:

    从 React 16.6.0(2018 年 10 月 23 日)开始,React.lazy() 可能是一个方便的解决方案,在 code-splitting 的旗帜下用于客户端渲染。

    固定组件名称

    鉴于您希望延迟加载以下组件 (MyLazy.js):

    import React from 'react';
    
    export default function (props) {
      return (
        <p>My component</p>
      );
    }
    

    您可以使用以下内容从另一个组件(例如 App.js)渲染它,注意内置的 &lt;Suspense&gt; 组件将一直显示,直到加载完成:

    import React, { Suspense } from 'react';
    
    export function App(props) {
      const MyLazyLoaded = React.lazy(() => import('./MyLazy'));
      return (
        <div>
          <Suspense fallback={<div>Loading...</div>}>
            <MyLazyLoaded />
          </Suspense>
        </div>
      );
    
    }
    

    动态组件名称

    如果要动态设置组件的名称,例如根据您的问题,根据用户输入,您可以将导入语句设为变量,例如:

    var lazyComponentName = 'MyLazy';
    const MyLazyLoaded = React.lazy(() => import('./' + lazyComponentName));
    

    请注意,如果您不直接连接并使用变量名(即import(lazyComponentName)),您将看到以下与webpack 相关的ES Lint 警告:

    关键依赖:依赖的请求是一个表达式

    【讨论】:

      【解决方案5】:

      由于我的问题有点不同,我有一个不同的解决方案,我寻找了一段时间但找不到答案。希望这可以帮助某人。

      我的问题是,我们将多个应用程序独立部署为核心 UI 中的不同模块。这些必须完全独立部署,不会对其他产生任何影响。

      我的解决方案是通过设置这些 webpack 属性将子组件应用程序包装为 webpack 库:

      libraryTarget: 'umd',
      globalObject: 'this',
      library: 'SubComponentName'
      

      我认为基于直接访问或通过单独应用程序中的父组件访问应用程序为应用程序提供了 2 个入口点

      class Library extends Component {
      
          render() {
              return (
                  < Provider store = {store} >< BrowserRouter>
                      < App isAccessDirect={this.props && this.props.isAccessDirect || false} roles={this.props.roles}>
                      </App>
                  < /BrowserRouter>< /Provider>
              )
          }
      }
      
      class Launcher extends Component {
          render() {
              return (
                  ReactDOM.render(
                      <Library isAccessDirect="true"/>,
                      document.getElementById('root')
                  )
              )
          }
      }
      

      构建此应用后,我可以直接在 index.html 中使用

      <script>
          new Compression.Launcher().render();
      </script>
      

      或者,如果您希望按照我的要求通过单独的主机/组件访问应用程序,您可以结合使用 loadjs 和 react。

      loadjs(['../sub/component/bundle.min.js'], 'loadSubComponent');
      
      var subComponent = this.getUI('subComponentWrapperElement').get(0);
      
      loadjs.ready('loadSubComponent', function() {
      
          var roles = my.app.roles;
      
          ReactDOM.render(
              <SubComponentName.Library roles={roles}></SubComponentName.Library>,
              subComponent
          );
      });
      

      这可以在任何类型的 javascript 客户端框架中加载到反应组件中,在我们的例子中,我们在包装应用程序中使用了主干。 bundle.min.js 也位于反向代理的后面,但它可以位于任何地方,只需将其加载并等待加载完成,这在这种情况下 loadjs 是完美的。最后一点重要的信息是,销毁组件对于确保良好的用户体验至关重要,否则我们会遇到许多问题。

      loadjs.reset();
      var subComponent = this.getUI('subComponentWrapperElement').get(0);
      ReactDOM.unmountComponentAtNode(subComponent);
      

      因此,简而言之,我认为与其他答案相比,它提供的是一套完全独立可部署的应用程序套件,可以完全与框架无关。

      【讨论】:

        【解决方案6】:

        我正在寻找解决方案,然后我发现了 react-router npm 包。

        https://www.npmjs.com/package/react-loadable
        

        这是我的例子:

        动态加载带有名称的组件。首先获取加载项:

          const MyComponent = Loadable({
            loader: () => import(`../charts/${some_name}`),
            loading: () => <div>Loading...</div>,
          });
        

        然后你有了它,你可以渲染它:

        <MyComponent />
        

        此阅读可能也有帮助:

        https://itnext.io/react-loading-components-dynamically-a9d8549844c4
        

        【讨论】:

          【解决方案7】:

          这里有很多很好的答案。我发现的另一种方法是将组件延迟加载为微应用。宿主应用程序甚至不需要在设计时安装或导入组件。

          import React from 'react';
          import ReactDOM from 'react-dom';
          import MicroApp from '@schalltech/honeycomb-react-microapp';
          
          const App = () => {
            return (
              <MicroApp
                  config={{
                    View: {
                      Name: 'redbox-demo',
                      Scope: 'beekeeper',
                      Version: 'latest'
                    }
                  }}
                />
            );
          });
          
          export default App;
          

          我按照下面的链接对此进行了实验。这是一个非常有趣的概念。

          https://github.com/Schalltech/honeycomb-marketplace

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2022-07-19
            • 1970-01-01
            • 2017-10-12
            • 2018-09-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多