简体   繁体   English

动态加载 React 组件

[英]Dynamically loading React components

I'm thinking about building a web application, where people can install plugins.我正在考虑构建一个 Web 应用程序,人们可以在其中安装插件。 I'd like plugins to be able to define React components that will be rendered to the page, without recompiling the main JavaScript bundle after installing the plugin.我希望插件能够定义将呈现到页面的 React 组件,而无需在安装插件后重新编译主 JavaScript 包。

So here's the approach I'm thinking of:所以这是我正在考虑的方法:

  • Bundle the main JavaScript with React as an external library , using webpack.使用 webpack 将主要 JavaScript 与 React 作为外部库捆绑在一起。
  • Have plugin authors compile their components with React as an external library as well.让插件作者也使用 React 作为外部库编译他们的组件。

This way, I'm only running one instance of React.这样,我只运行了一个 React 实例。 I could probably do the same with some other frequently used libraries.我可能可以对其他一些经常使用的库做同样的事情。

The problem then, is how to dynamically load these plugin components from the server.那么问题是如何从服务器动态加载这些插件组件。 Let's say I have the following component:假设我有以下组件:

class PluginRenderer extends React.Component{
  componentWillMount() {
    getPluginComponent(`/plugins/${this.props.plugin}/component.js`).then((com) => {
      this.setState({pluginComponent: com});
    })
  }

  render() {
    var Plugin = this.state.pluginComponent;
    return Plugin ? <Plugin {...this.props} /> : "Loading..."
  }
}

How could getPluginComponent be implemented?如何实现getPluginComponent

It's an interesting problem I also faced some months ago for customer work, and I didn't see too many document approaches out there.这是几个月前我在客户工作中也面临的一个有趣问题,我没有看到太多的文档方法。 What we did is:我们所做的是:

  1. Individual plugins will be separate Webpack projects, for which we provide either a template or a CLI tool that generates project templates.单个插件将是单独的 Webpack 项目,我们为其提供模板或生成项目模板的 CLI 工具。

  2. In this project we define Webpack externals for shared vendor libraries already used in the core application: React, Redux, etc. This tells the plugin to not include those in the bundle but to grab them from a variable in window we set in the core app.在这个项目中,我们为核心应用程序中已经使用的共享供应商库定义了 Webpack externals :React、Redux 等。这告诉插件不要将它们包含在包中,而是从我们在核心应用程序中设置的window的变量中获取它们. I know, sounds like sucks, but it's much better than having all plugins re-include 1000s of shared modules.我知道,听起来很糟糕,但这比让所有插件重新包含 1000 个共享模块要好得多。

  3. Reusing this concept of external , the core app also provides some services via window object to plugins.重用这个external概念,核心应用程序还通过 window 对象向插件提供了一些服务。 Most important one is a PluginService.register() method which your plugin must call when it's initialized.最重要的一个是PluginService.register()方法,您的插件在初始化时必须调用该方法。 We're inverting control here: the plugin is responsible to say "hi I'm here, this is my main export (the Component if it's a UI plugin)" to the core application.我们在这里反转控制:插件负责向核心应用程序说“嗨,我在这里,这是我的主要导出(组件,如果它是 UI 插件)”。

  4. The core application has a PluginCache class/module which simply holds a cache for loaded plugins (pluginId -> whatever the plugin exported, fn, class, whatever).核心应用程序有一个 PluginCache 类/模块,它只保存加载插件的缓存(pluginId -> 无论插件导出,fn,类,等等)。 If some code needs a plugin to render, it asks this cache for it.如果某些代码需要一个插件来呈现,它会向这个缓存请求它。 This has the benefit of allowing to return a <Loading /> or <Error /> component when a plugin did not load correctly, and so on.这样做的好处是允许在插件未正确加载时返回<Loading /><Error />组件,等等。

  5. For plugin loading, this PluginService/Manager loads the plugin configuration (which plugins should I load?) and then creates dynamically injected script tags to load each plugin bundle.对于插件加载,这个 PluginService/Manager 加载插件配置(我应该加载哪些插件?)然后创建动态注入的script标签来加载每个插件包。 When the bundle is finished, the register call described in step 3 will be called and your cache in step 4 will have the component.当 bundle 完成时,第 3 步中描述的register调用将被调用,第 4 步中的缓存将包含该组件。

  6. Instead of trying to load the plugin directly from your component, ask for it from the cache.不要尝试直接从您的组件加载插件,而是从缓存中请求它。

This is a very high level overview which is pretty much tied to our requirements back then (it was a dashboard-like application where users could add/remove panels on the fly, and all those widgets were implemented as plugins).这是一个非常高级的概述,它与我们当时的需求密切相关(它是一个类似仪表板的应用程序,用户可以在其中动态添加/删除面板,并且所有这些小部件都是作为插件实现的)。

Depending on your case, you could even wrap the plugins with a <Provider store={ theCoreStore } > so they have to access to Redux, or setup an event bus of some kind so that plugins can interact with each other... There is plenty of stuff to figure out ahead.根据您的情况,您甚至可以使用<Provider store={ theCoreStore } > 包装插件,以便它们必须访问 Redux,或者设置某种事件总线,以便插件可以相互交互......有很多东西要提前弄清楚。 :) :)

Good luck, hope it helped somehow!祝你好运,希望它以某种方式有所帮助!

There is a HOC component that you can import to do this.您可以导入一个 HOC 组件来执行此操作。 Components are dynamically loaded as micro apps into your host application.组件作为微型应用程序动态加载到您的主机应用程序中。

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

window.React = React;
window.ReactDOM = ReactDOM;

ReactDOM.render(<App />, document.getElementById('root'));

// app.js
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;

The components are not installed or known at design time.这些组件在设计时未安装或已知。 If you get creative, using this approach you could update your components without needing to redeploy your host application.如果您有创意,使用这种方法您可以更新您的组件,而无需重新部署您的主机应用程序。

https://github.com/Schalltech/honeycomb-marketplace#using-micro-apps https://github.com/Schalltech/honeycomb-marketplace#using-micro-apps

I presented an alternative approach on a similar question .在一个类似的问题上提出了另一种方法 Recapping:回顾:

On your app在您的应用上

import(/* webpackIgnore: true */'https://any.url/file.js')
  .then((plugin) => {
    plugin.main({ /* stuff from app plugins need... */ });
  });

On your plugin...在你的插件...

const main = (args) => console.log('The plugin was started.');
export { main };
export default main;

See more details on the other question's page .在其他问题的页面上查看更多详细信息。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM