【问题标题】:Dynamically inject data in React Router Routes在 React Router Routes 中动态注入数据
【发布时间】:2019-12-22 02:15:22
【问题描述】:

我一直在尝试模块化我的 React.js 应用程序(将作为桌面应用程序与 Electron 一起交付),如果我将来制作一个新模块,我可以添加一个新文件夹并修改几个文件,它应该可以很好地集成。 我最初是受到这篇文章的启发:https://www.nylas.com/blog/react-plugins/

在那之后,我开始尽可能多地进行研究,并最终创建了一个 JSON 文件,该文件将存在于服务器中,其中包含为该特定客户端注册的插件清单。 像这样的:

{
    "plugins": [
        {
            "name": "Test Plugin",
            "version": "0.0.1",
            "path": "testplugin",
            "file": "test",
            "component":"TestPlugin"
        },
        {
            "name": "Another Plugin",
            "version": "0.0.1",
            "path": "anothertest",
            "file": "othertest",
            "component":"TestPluginDeux"
        }
    ]
}

之后,我创建了几个与 path 值匹配的文件夹,并且包含与清单中的名称匹配的组件(例如,testplugin/test.jsx 默认导出 TestPlugin 组件)。我还制作了一个pluginStore 文件,用于读取清单并将插件安装在this.state 中。

然后,在 Google 和此处进行了大量研究并找到了这个答案:React - Dynamically Import Components

使用该函数,我能够遍历清单,找到目录中的文件夹,并通过运行我在 pluginStore 中创建的 mountPlugins() 函数将插件安装在 this.state 中,在componentDidMount() 方法在我的主页。

到目前为止一切顺利。我正在使用 React-Router,我能够在 State 中动态挂载插件,并能够通过像这样调用它们将它们加载到我的 Home Route 中:<TestPlugin />

我现在遇到的问题是,我想通过使用componentrender 方法动态创建将从状态加载这些组件的路由,但我没有运气。我总是会得到相同的结果......显然我传递的是一个对象而不是一个字符串。

这是我这次尝试的最后一次迭代:

{this.state.modules.registered.map((item) =>
<Route exact path={`/${item.path}`} render={function() {
  return <item.component />
  }}></Route>
)}

之后,我创建了一个调用PluginShell 组件的路由,该组件由Navlink 调用,发送插件名称以动态注入和加载。

<Route exact path='/ex/:component' component={PluginShell}></Route>

但我最终遇到了同样的问题。我正在传递一个object,而createElement 函数需要一个string

我搜索了整个 StackOverflow,发现了许多类似的问题和答案。我尝试应用所有可能的解决方案,但没有成功。

编辑: 我已经整理了一个 GitHub 存储库,其中包含最少的文件集来重现该问题。

这是链接: https://codesandbox.io/embed/aged-moon-nrrjc

【问题讨论】:

  • 你在使用动态导入import()吗?它返回一个解析为对象的承诺。如果您使用export default MyComponent 导出,它将解析为对象{ default: MyComponent }
  • @RamilAmparo,我在 componentDidMount() 方法中使用动态导入。在动态导入中,我正在运行一个名为“mountPlugins”的函数,它将它们添加到this.state。组件默认导出。
  • 我们可以看看pluginStore.getAll().registered.plugins 正在返回什么。它可能没有返回有效的组件。
  • @RamilAmparo 用截图更新了问题。 :)
  • 不要发布部分代码和屏幕截图,请附上 mwe:stackoverflow.com/help/minimal-reproducible-example。一个 github repo 将是理想的,即使它被剥离为没有任何样式的纯组件。

标签: javascript reactjs react-router modularization


【解决方案1】:

我认为问题在于您尝试渲染 &lt;item.component /&gt; 的方式,但不确定,您是否遇到同样的错误?

试试:

<Route exact path={`/${item.path}`} render={function() {
  return React.createElement(item.component, props)
  }}></Route>
)}

【讨论】:

  • 感谢您的意见!我将您的建议作为复制/粘贴进行了测试,并将 props 抛出为未定义,将其删除并且没有可见的错误,但不起作用。我在控制台中得到了这个:Warning: &lt;TestPlugin /&gt; is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements. in TestPlugin (created by Context.Consumer)
  • 是的,我的错把props 作为参考来展示如何在 casayou 中传递道具,但对于海豚来说应该避免这种情况。您可以尝试将component 名称更改为Component 吗?
  • 我改变了情况。在我的manifest.json 中(现在是"Component": "TestPlugin" 和我的map() 函数中(component={item.Component})。警告消失了,但仍然没有加载。
【解决方案2】:

你的想法是对的,如果我猜你的语法有点不对劲的话。我无需对您的示例进行太多调整即可获得动态路由工作。

这是我认为您想要做的一个工作示例:

const modules = [{
  path: '/',
  name: 'Home',
  component: Hello
},{
  path: '/yo',
  name: 'Yo',
  component: Yo
}];


function DynamicRoutes() {
    return (
        <BrowserRouter>
            { modules.map(item => <Route exact path={item.path} component={item.component}/>) }
        </BrowserRouter>
    );
}

https://stackblitz.com/edit/react-zrdmcq

【讨论】:

  • 感谢您的意见!我将我的结构与您的小提琴相匹配(使用Link 而不是Navlink,添加exact 属性,使用相同的map() 函数结构但仍然没有运气。这就是我在控制台中得到的:Warning: &lt;TestPlugin /&gt; is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements. in TestPlugin (created by Context.Consumer)跨度>
  • @AlfieRobles 如果您使用的是自定义反应组件,则组件必须以大写字母 &lt;Component /&gt; 而不是 &lt;component /&gt; 开头。如果它以小写字母开头,react 可能会将其与 HTML 元素混淆,例如 &lt;div /&gt;
  • 您能告诉我们TestPlugin 的样子吗? React 不会将其识别为 React 组件。
  • @RamilAmparo 我改变了情况。在我的 manifest.json 中(现在是 "Component": "TestPlugin" 和在我的 map() 函数中 component={item.Component}。警告消失了,但仍然没有加载。
  • @UncleDave 这就是它的样子。 const TestPlugin = () =&gt; ( &lt;div className='home'&gt; &lt;h1&gt;I'M A TEST PLUGIN! :D&lt;/h1&gt; &lt;/div&gt; ); export default TestPlugin;
【解决方案3】:

好吧。这里有很多活动部件可以大大简化。

  1. 我建议转向对开发人员更友好、更固执己见的国有商店(如Redux)。我个人从未使用过 Flux,所以我只能推荐我使用过的东西。因此,您可以避免使用普通类进行状态管理。
  2. 您应该只在初始应用程序加载期间导入一次模块,然后您可以调度一个操作将它们存储到 (Redux) 状态,然后根据需要与组件共享状态(仅在要共享状态时才需要)许多组件分布在您的 DOM 树中,否则根本不需要)。
  3. 模块导入是异步的,因此无法立即加载。您必须设置一个条件以等待模块被加载,然后再将它们映射到Route(在您的情况下,您试图将模块的注册字符串名称映射到路由,而不是导入的模块函数)。
  4. 理想情况下,模块导入应包含在状态内的已注册模块中。换句话说,当你导入模块时,它应该只是用一个组件函数覆盖模块组件字符串。这样一来,所有相关信息都放在一个对象中。
  5. 无需将模板文字与字符串连接混合和匹配。使用其中一种。
  6. 在覆盖之前使用setState callback 传播任何previousState。看起来更简单、更干净。
  7. 将您的 import 语句包装在 try/catch 块中,否则,如果该模块不存在,它可能会破坏您的应用程序。

工作示例(我只是在这个简单的示例中使用 React 状态,我也没有触及任何其他文件,这也可以简化):


App.js

import React from "react";
import Navigation from "./components/MainNavigation";
import Routes from "./routes";
import { plugins } from "./modules/manifest.json";
import "./assets/css/App.css";

class App extends React.Component {
  state = {
    importedModules: []
  };

  componentDidMount = () => {
    this.importPlugins();
  };

  importPlugins = () => {
    if (plugins) {
      try {
        const importedModules = [];
        const importPromises = plugins.map(plugin =>
          import(`./modules/${plugin.path}/${plugin.file}`).then(module => {
            importedModules.push({ ...plugin, Component: module.default });
          })
        );

        Promise.all(importPromises).then(() =>
          this.setState(prevState => ({
            ...prevState,
            importedModules
          }))
        );
      } catch (err) {
        console.error(err.toString());
      }
    }
  };

  render = () => (
    <div className="App">
      <Navigation />
      <Routes {...this.state} />
    </div>
  );
}

export default App;

routes/index.js

import React from "react";
import React from "react";
import isEmpty from "lodash/isEmpty";
import { Switch, Route } from "react-router-dom";
import ProjectForm from "../modules/core/forms/new-project-form";
import NewPostForm from "../modules/core/forms/new-post-form";
import ProjectLoop from "../modules/core/loops/project-loop";
import Home from "../home";

const Routes = ({ importedModules }) => (
  <Switch>
    <Route exact path="/" component={Home} />
    <Route exact path="/projectlist/:filter" component={ProjectLoop} />
    <Route exact path="/newproject/:type/:id" component={ProjectForm} />
    <Route exact path="/newpost/:type" component={NewPostForm} />
    {!isEmpty(importedModules) &&
      importedModules.map(({ path, Component }) => (
        <Route key={path} exact path={`/${path}`} component={Component} />
      ))}
  </Switch>
);

export default Routes;

【讨论】:

  • 哦,哇!太感谢了!我编辑了我的问题,但它最初说我是 React 的新手,并且清楚地表明了这一点。我非常感谢你的帮助!我刚刚实现了我的代码,它运行良好。我还将根据您的建议进行研究并尝试修复其余文件。 :)
  • 另外,我实际上想知道异步导入。我想如果他们正在加载一个块,我可能需要一个不同的函数来在它们安装后捕获它们。我正在审查您提供的所有更改,并尝试从中学习。
  • 我建议先从本质上更简单的东西开始,然后再进行异步模块导入。如果你是 React 新手,我强烈推荐这门课程:udemy.com/share/1000NiA0UZdFlWQno=(目前只需 20 美元,涵盖了 React/Redux/Webpack 的大部分内容,是一项值得投资,特别是如果你认真学习 Web 开发) .
猜你喜欢
  • 2023-01-09
  • 1970-01-01
  • 2018-06-11
  • 2020-07-22
  • 2019-03-29
  • 2018-08-28
  • 2021-05-13
  • 2022-10-23
  • 1970-01-01
相关资源
最近更新 更多