我们对微服务的了解是:它有助于单独开发、部署和维护应用程序。就像 Uber 一样,预订和支付服务是单独开发和部署的。同样,我们可以单独开发、部署 React 应用程序。这意味着,您的应用程序被划分为各种单独的应用程序并且易于维护,这个概念被称为微前端。
我们将看到一个使用微前端的 React 网站,我们在其中创建了三个应用程序,博客应用程序、标题应用程序和容器应用程序(它们粘合博客和标题并代表一个 Web 应用程序)。
请从我们的 GitHub 下载完整的源代码。
让我们创建三个react应用程序,
- 博客(网站博客应用)
- 标题(网站标题)
- 容器(实际网站,我们合并博客和标题的地方)
npx create-react-app container npx create-react-app blogs npx create-react-app header
|
1. 博客应用程序
让我们创建常量添加博客数组,
export const arrBlogs = [ { "blogID": 1, "blogName": "React Navigation", "blogDetail": "Hello Developers! Let's see what's new in React Navigation 6.x.", "blogURL": "https://www.kpiteng.com/blogs/react-nativagation-6.x" }, { "blogID": 2, "blogName": "Securing React Native Application", "blogDetail": "Discover a ways to develop secure react native application.", "blogURL": "https://www.kpiteng.com/blogs/securing-react-native-application" }, { "blogID": 3, "blogName": "Top 10 React Tricks Every Developer Should Use", "blogDetail": "Discover a set of React best coding practices, tips and tricks that should be known by all developers.", "blogURL": "https://www.kpiteng.com/blogs/top-10-react-tricks-every-developer-should-use" } ]
|
为博客列表编写代码,创建一个文件Blog.js:
import React, { useState, useEffect } from "react"; import {arrBlogs} from './Constant'; import { Link } from "react-router-dom"; import "./App.css";
function App() { return ( <div className="container mt-5"> <div className="row"> { arrBlogs.map((blog, index) => { return ( <div className="col-xs-12 col-sm-12 col-md-6 col-lg-4 col-xl-4 mb-5"> <div className="card"> <Link to={{pathname: <code>/blogdetail/${blog.blogID}</code>, id: blog.blogID, item: blog}} > <div class="card-body"> <h5 class="card-title">{<code>#${blog.blogID}</code>}</h5> <p class="card-text">{blog.blogName}</p> <p class="card-text">{blog.blogDetail}</p> </div> </Link> </div> </div> ) }) } </div> </div> ); }
export default App;
|
博客位于 url.com/blogs,所以我们需要设置react-router-dom 和 history。
yarn add react-router-dom history
要查看博客详细信息,我们需要为 BlogDetail 编写代码,创建文件BlogDetail.js
import React, { useState, useEffect } from "react"; import {arrBlogs} from './Constant'; import "./App.css";
function BlogDetail(props) {
const [blogDetail, setBlogDetail] = useState({});
useEffect(() => { const blogID = parseInt(props.match.params.blogid); const index = arrBlogs.findIndex((blog) => blog.blogID === blogID); if (index !== -1){ setBlogDetail(arrBlogs[index]) } }, []);
return ( <div className="container mt-5"> <div className="row"> <div className="card"> { Object.keys(blogDetail).length > 0 && <> <p>{<code>#${blogDetail.blogID}</code>}</p> <p>{blogDetail.blogName}</p> <p>{blogDetail.blogDetail}</p> <p>{blogDetail.blogURL}</p> </> } { Object.keys(blogDetail).length === 0 && <p>We're sorry, Cound't find Blog</p> } </div> </div> </div> ); }
export default BlogDetail;
|
最后,我们有了 Constant、Blogs 和 BlogDetail。现在让我们为博客编写代码,BlogDetail 路由。将 App.js 代码替换为以下内容,
import React, { useState, useEffect } from "react"; import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'; import { createBrowserHistory } from "history"; import Blogs from './Blogs'; import BlogDetail from './BlogDetail'; import "./App.css";
const defaultHistory = createBrowserHistory();
function App({ history = defaultHistory }) { return ( <Router> <Switch> <Route exact path="/" component={Blogs} /> <Route exact path="/blogdetail/:blogid" component={BlogDetail} /> </Switch> </Router> ); }
export default App;
|
现在,是时候运行应用程序了。我们可以看到博客列表,点击博客后,它会将用户重定向到博客详细信息。
2. 标题应用
在这里,我们简单地添加 header div 来演示。因此,让我们添加所有必需的依赖项。
yarn add react-router-dom history
我们修改 App.js 的代码:
import React from "react"; import { createBrowserHistory } from "history"; import "./App.css";
const defaultHistory = createBrowserHistory();
function App({ history = defaultHistory }) { return ( <div> <p>KPITENG (Header Application)</p> </div> ); }
export default App;
|
现在,让我们运行应用程序,它将显示一个简单的标题。
3. 容器应用
现在,是时候设置我们的容器应用程序,它实际使用/合并标题和博客应用程序到我们的容器应用程序(我们的主网站)中
让我们将 react-router-dom, history 添加到容器应用程序中。之后让我们更新 App.js 的代码
import React, { useState } from "react"; import { BrowserRouter, Switch, Route } from "react-router-dom"; import { createBrowserHistory } from "history"; import MicroFrontend from "./MicroFrontend";
import "./App.css";
const defaultHistory = createBrowserHistory();
const { REACT_APP_HEADER_HOST: headerHost, REACT_APP_BLOGS_HOST: blogHost, } = process.env;
function Header({ history }) { return <MicroFrontend history={history} host={headerHost} name="Header" />; }
function Blogs({ history }) { return <MicroFrontend history={history} host={blogHost} name="Blogs" />; }
function BlogDetail({history}) { return ( <div> <MicroFrontend history={history} host={blogHost} name="Blogs" /> </div> ); }
function Home({ history }) {
return ( <div className="container"> <Header /> <Blogs /> </div> ); }
function App() { return ( <BrowserRouter> <React.Fragment> <Switch> <Route exact path="/" component={Home} /> <Route exact path="/blogdetail/:blogid" component={BlogDetail} /> </Switch> </React.Fragment> </BrowserRouter> ); }
export default App;
|
设置微前端 -
想一想,我的容器应用程序是如何知道标题应用程序和博客应用程序的。让我们一一设置。
* 设置Web 应用程序端口 - *容器应用程序 - 端口 3000标题应用程序 - 端口 3001博客应用程序 - 端口 3002
为此,请更新 package.json,
容器应用:
"scripts": { "start": "PORT=3000 react-app-rewired start", },
|
标题应用:
"scripts": { "start": "PORT=3001 react-app-rewired start", },
|
博客应用:
"scripts": { "start": "PORT=3002 react-app-rewired start", },
|
现在,在容器应用程序的根目录中创建 .env 文件,
REACT_APP_HEADER_HOST= http://localhost:3001
REACT_APP_BLOGS_HOST= http://localhost:3002
React App 将整个应用程序捆绑到 main.js,我们有渲染、挂载、卸载组件的功能。
Render Function Name: render{ApplicationName} UnMount Function Name: unmount{ApplicationName}
|
所以,你的博客应用程序看起来像:
renderBlogs
unmountBlogs
同样的方式,标题应用 看起来像,
renderHeader
unmountHeader
我们在Container App中创建一个MicroFrontend.js文件,里面有挂载、卸载组件的业务逻辑。
import React, { useEffect } from "react";
function MicroFrontend({ name, host, history }) { useEffect(() => { const scriptId = <code>micro-frontend-script-${name}</code>;
const renderMicroFrontend = () => {
window<a href="<code>${name}-container</code>, history"><code>render${name}</code></a>; };
if (document.getElementById(scriptId)) { renderMicroFrontend(); return; }
fetch(<code>${host}/asset-manifest.json</code>) .then((res) => res.json()) .then((manifest) => { const script = document.createElement("script"); script.id = scriptId; script.crossOrigin = ""; script.src = <code>${host}${manifest.files["main.js"]}</code>; script.onload = () => { renderMicroFrontend(); }; document.head.appendChild(script); });
return () => { window<a href="<code>${name}-container</code>"><code>unmount${name}</code>] && window[<code>unmount${name}</code></a>; }; });
return <main id={<code>${name}-container</code>} />; }
MicroFrontend.defaultProps = { document, window, };
export default MicroFrontend;
|
如您所见,MicroFrontend 组件将名称、主机和历史记录作为参数。查看从主机获取资产清单.json 并使用 main.js 创建脚本对象的 fetch 函数,它将使用渲染函数来挂载组件。
为博客应用程序设置微前端
让我们安装 react-app-rewired 包,它会在不弹出应用程序的情况下覆盖构建配置。
yarn add react-app-rewired
在博客应用的根目录下创建 config.overrides.js 并添加以下代码。
module.exports = { webpack: (config, env) => { config.optimization.runtimeChunk = false; config.optimization.splitChunks = { cacheGroups: { default: false, }, }; config.output.filename = "static/js/[name].js"; config.plugins[5].options.filename = "static/css/[name].css"; config.plugins[5].options.moduleFilename = () => "static/css/main.css"; return config; }, };
|
现在,让我们更新 package.json 文件的脚本部分:
"scripts": { "start": "PORT=3002 react-app-rewired start", "build": "react-app-rewired build", "test": "react-app-rewired test", "eject": "react-app-rewired eject" },
|
以及博客应用程序中更新 index.js 的最后一步,
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App';
window.renderBlogs = (containerId, history) => { ReactDOM.render( <App history={history} />, document.getElementById(containerId), ); };
window.unmountBlogs = containerId => { ReactDOM.unmountComponentAtNode(document.getElementById(containerId)); };
if (!document.getElementById('Blogs-container')) { ReactDOM.render(<App />, document.getElementById('root')); }
|
为标题应用程序设置微前端 -
- 安装 react-app-rewired
- 更新 package.json
- 更新 index.js 文件
最后,运行容器应用程序(我们的主 Web 应用程序)