在 React Router 路由中动态注入数据
Dynamically inject data in React Router Routes
我一直在尝试模块化我的 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 进行了大量研究,并在此处找到了这个答案:
使用该函数,我能够遍历清单,找到目录中的文件夹,并通过 运行 mountPlugins()
函数将插件安装在 this.state
中在 pluginStore
中创建,在我主页的 componentDidMount()
方法中。
到目前为止一切顺利。我正在使用 React-Router,我能够在 State 中动态安装插件,并能够通过这样调用它们来将它们加载到我的 Home Route 中:<TestPlugin />
.
我现在遇到的问题是,我想通过使用 component
或 render
方法动态创建将从状态加载这些组件的路由,但我有没有运气。我总是会得到相同的结果...显然我传递的是对象而不是字符串。
这是我这次尝试的最后一次迭代:
{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
.
我搜索了整个 Whosebug,发现了很多类似的问题和答案。我尝试了所有可能的解决方案,但没有成功。
编辑:
我整理了一个 GitHub 存储库,其中包含可重现该问题的最少文件集。
我认为问题在于您尝试呈现的方式 <item.component />
但不确定,您是否遇到了同样的错误?
尝试:
<Route exact path={`/${item.path}`} render={function() {
return React.createElement(item.component, props)
}}></Route>
)}
你的想法是正确的,如果有的话我猜你的语法有点不对劲。我无需根据您的示例进行太多调整即可获得动态路由。
这是我认为你想要做的工作示例:
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>
);
}
好啦好啦。这里有很多活动部件可以大大简化。
- 我建议转向更 developer-friendly、固执己见的状态存储(例如 Redux)。我个人从未使用过 Flux,所以我只能推荐我使用过的东西。因此,您可以避免使用简单的 类 进行状态管理。
- 您应该只在初始应用程序加载期间导入模块一次,然后您可以分派一个操作将它们存储到 (Redux) 状态,然后根据需要与组件共享状态(仅当状态是与分布在 DOM 树中的许多组件共享,否则根本不需要)。
- 模块导入是异步的,因此不能立即加载。在将模块映射到
Route
之前,您必须设置一个条件来等待加载模块(在您的情况下,您试图将模块的注册字符串名称映射到路由,而不是导入的模块功能)。
- 理想情况下,模块导入应该包含在状态中的已注册模块中。换句话说,当您导入模块时,它应该只是用组件函数覆盖模块组件字符串。这样一来,所有相关信息都放在一个对象中。
- 不需要混合和匹配模板文字与字符串连接。使用其中之一。
- 在覆盖之前使用 setState callback 传播任何
previousState
。看起来更简单、更干净。
- 将您的导入语句包裹在
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.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 进行了大量研究,并在此处找到了这个答案:
使用该函数,我能够遍历清单,找到目录中的文件夹,并通过 运行 mountPlugins()
函数将插件安装在 this.state
中在 pluginStore
中创建,在我主页的 componentDidMount()
方法中。
到目前为止一切顺利。我正在使用 React-Router,我能够在 State 中动态安装插件,并能够通过这样调用它们来将它们加载到我的 Home Route 中:<TestPlugin />
.
我现在遇到的问题是,我想通过使用 component
或 render
方法动态创建将从状态加载这些组件的路由,但我有没有运气。我总是会得到相同的结果...显然我传递的是对象而不是字符串。
这是我这次尝试的最后一次迭代:
{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
.
我搜索了整个 Whosebug,发现了很多类似的问题和答案。我尝试了所有可能的解决方案,但没有成功。
编辑: 我整理了一个 GitHub 存储库,其中包含可重现该问题的最少文件集。
我认为问题在于您尝试呈现的方式 <item.component />
但不确定,您是否遇到了同样的错误?
尝试:
<Route exact path={`/${item.path}`} render={function() {
return React.createElement(item.component, props)
}}></Route>
)}
你的想法是正确的,如果有的话我猜你的语法有点不对劲。我无需根据您的示例进行太多调整即可获得动态路由。
这是我认为你想要做的工作示例:
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>
);
}
好啦好啦。这里有很多活动部件可以大大简化。
- 我建议转向更 developer-friendly、固执己见的状态存储(例如 Redux)。我个人从未使用过 Flux,所以我只能推荐我使用过的东西。因此,您可以避免使用简单的 类 进行状态管理。
- 您应该只在初始应用程序加载期间导入模块一次,然后您可以分派一个操作将它们存储到 (Redux) 状态,然后根据需要与组件共享状态(仅当状态是与分布在 DOM 树中的许多组件共享,否则根本不需要)。
- 模块导入是异步的,因此不能立即加载。在将模块映射到
Route
之前,您必须设置一个条件来等待加载模块(在您的情况下,您试图将模块的注册字符串名称映射到路由,而不是导入的模块功能)。 - 理想情况下,模块导入应该包含在状态中的已注册模块中。换句话说,当您导入模块时,它应该只是用组件函数覆盖模块组件字符串。这样一来,所有相关信息都放在一个对象中。
- 不需要混合和匹配模板文字与字符串连接。使用其中之一。
- 在覆盖之前使用 setState callback 传播任何
previousState
。看起来更简单、更干净。 - 将您的导入语句包裹在
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;