存储和设置登录状态
由于每个微应用都有可能用到登录状态以及设置登录状态的方法,所以存储和设置登录状态的方法需要放在容器应用中,哪个微应用需要,就传递给它。
创建登录状态和设置方法,并传递给微应用组件:
// container\src\App.js
import React, { lazy, Suspense, useState, useEffect } from 'react'
import { Router, Route, Switch } from 'react-router-dom'
import { createBrowserHistory } from 'history'
import Header from './components/Header'
// 懒加载微应用
const MarketingApp = lazy(() => import('./components/MarketingApp'))
const AuthApp = lazy(() => import('./components/AuthApp'))
const history = createBrowserHistory()
function App() {
// 创建登录状态和设置方法
const [status, setStatus] = useState(false)
useEffect(() => {
console.log(status)
}, [status])
return (
<Router history={history}>
<Header />
<Suspense fallback={<div>正在加载组件</div>}>
<Switch>
<Route path="/auth/signin">
<AuthApp setStatus={setStatus} />
</Route>
<Route path="/">
<MarketingApp />
</Route>
</Switch>
</Suspense>
</Router>
)
}
export default App
// container\src\components\AuthApp.js
import React, { useEffect, useRef } from 'react'
import { mount } from 'auth/AuthApp'
import { useHistory } from 'react-router-dom'
export default function AuthApp({ setStatus }) {
const ref = useRef()
const history = useHistory()
useEffect(() => {
const { onParentNavigate } = mount(ref.current, {
onNavigate({ pathname: nextPathname }) {
const pathname = history.location.pathname
// 路由不同时更新路由
if (pathname !== nextPathname) history.push(nextPathname)
},
// 传递当前路由作为微应用的默认路由
initialPath: history.location.pathname,
// 设置登录状态的方法
setStatus
})
// 容器应用路由变化通知微应用
if (onParentNavigate) history.listen(onParentNavigate)
}, [])
return <div ref={ref}></div>
}
// auth\src\bootstrap.js
import React from 'react'
import ReactDOM from 'react-dom'
import { createMemoryHistory, createBrowserHistory } from 'history'
import App from './App'
function mount(el, { onNavigate, defaultHistory, initialPath, setStatus } = {}) {
const history = defaultHistory || createMemoryHistory({
// 指定默认匹配地址
initialEntries: [initialPath]
})
if (onNavigate) history.listen(onNavigate)
ReactDOM.render(<App history={history} setStatus={setStatus} />, el)
return {
onParentNavigate({ pathname: nextPathname }) {
const pathname = history.location.pathname
if (pathname !== nextPathname) {
history.push(nextPathname)
}
}
}
}
if (process.env.NODE_ENV === 'development') {
const el = document.querySelector('#dev-auth')
if (el) {
mount(el, {
defaultHistory: createBrowserHistory()
})
}
}
export { mount }
// auth\src\App.js
import React from 'react'
import { Router, Route, Switch } from 'react-router-dom'
import Signin from './components/Signin'
function App({ history, setStatus }) {
return (
<Router history={history}>
<Switch>
<Route path="/auth/signin">
<Signin setStatus={setStatus} />
</Route>
</Switch>
</Router>
)
}
export default App
// auth\src\components\Signin.js
import React from 'react'
export default function SignIn({ setStatus }) {
const styles = {
width: '400px',
margin: '20px auto'
}
return (
<div style={styles}>
<h2>登录</h2>
<form onSubmit={e => e.preventDefault()} noValidate>
<input type="email" name="email" placeholder="邮件地址" autoFocus />
<br />
<input type="password" name="password" placeholder="密码" />
<br />
<input type="checkbox" /> 记住我
<br />
<button type="submit" onClick={() => setStatus(true)}>
登录
</button>
</form>
</div>
)
}
登录状态应用
实现三个事情:
- 根据登录状态显示登录/退出链接
- 点击退出链接取消登录状态
- 如果已登录,跳转到 Dashboard 应用
向 Header 组件传递登录状态和设置方法:
// container\src\App.js
<Header status={status} setStatus={setStatus} />
Header 组件使用登录状态:
// container\src\components\Header.js
import React from 'react'
import { Link } from 'react-router-dom'
export default function Header({ status, setStatus }) {
const styles = {
backgroundColor: '#ddd',
padding: '10px',
display: 'flex',
justifyContent: 'space-between'
}
return (
<div style={styles}>
<Link to="/">首页</Link>
{status ? (
<Link to="/" onClick={() => setStatus(false)}>
退出
</Link>
) : (
<Link to="/auth/signin">登录</Link>
)}
</div>
)
}
修改监听 status
的地方:
// container\src\App.js
useEffect(() => {
// 如果已登录,跳转到 Dashboard
if (status) {
history.push('/dashboard')
}
console.log(status)
}, [status])
Dashboard - 应用初始化
Dashboard 应用使用 vue3 开发。
# 创建应用文件夹
mkdir dashboard
cd dashboard
# 初始化 package.json
npm init -y
# 安装生产依赖(共享模块)
npm install vue
# 安装开发依赖
npm install -D @babel/core @babel/plugin-transform-runtime @babel/preset-env babel-loader css-loader style-loader vue-loader vue-style-loader webpack webpack-cli webpack-dev-server webpack-merge html-webpack-plugin @vue/compiler-sfc
本例依赖版本如下:
"dependencies": {
"vue": "^3.2.31"
},
"devDependencies": {
"@babel/core": "^7.17.2",
"@babel/plugin-transform-runtime": "^7.17.0",
"@babel/preset-env": "^7.16.11",
"@vue/compiler-sfc": "^3.2.31",
"babel-loader": "^8.2.3",
"css-loader": "^6.6.0",
"html-webpack-plugin": "^5.5.0",
"style-loader": "^3.3.1",
"vue-loader": "^17.0.0",
"vue-style-loader": "^4.1.3",
"webpack": "^5.68.0",
"webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.7.4",
"webpack-merge": "^5.8.0"
}
修改启动脚本:
"scripts": {
"start": "webpack serve"
},
添加模板文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard</title>
</head>
<body>
<div id="dev-dashboard"></div>
</body>
</html>
添加入口文件:
// dashboard\src\index.js
import('./bootstrap')
添加 bootstrap.js:
// dashboard\src\bootstrap.js
import { createApp } from 'vue'
import Dashboard from './components/Dashboard'
function mount(el) {
const app = createApp(Dashboard)
app.mount(el)
}
if (process.env.NODE_ENV === 'development') {
const el = document.querySelector('#dev-dashboard')
if (el) {
mount(el)
}
}
export { mount }
添加 Dashboard 组件:
<!-- dashboard\src\components\Dashboard.vue -->
<template>
<div>Dashboard</div>
</template>
<script>
export default {
name: 'Dashboard',
data() {
return {
}
}
}
</script>
<style scoped></style>
添加 webpack 配置文件:
// dashboard\webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { VueLoaderPlugin } = require('vue-loader')
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin')
const packageJson = require('./package.json')
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
publicPath: 'http://localhost:8083/'
},
resolve: {
extensions: ['.js', '.vue']
},
devServer: {
port: 8083,
historyApiFallback: true
},
module: {
rules: [
{
test: /\.vue$/,
use: 'vue-loader'
},
{
test: /\.css$/,
use: ['vue-style-loader', 'style-loader', 'css-loader']
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: ['@babel/plugin-transform-runtime']
}
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html'
}),
new VueLoaderPlugin(),
new ModuleFederationPlugin({
name: 'dashboard',
filename: 'remoteEntry.js',
exposes: {
'./DashboardApp': './src/bootstrap'
},
shared: packageJson.dependencies
})
]
}
npm start
启用应用,访问 http://localhost:8083
Container - 加载 Dashboard 应用
添加微应用配置:
// container\webpack.config.js
remotes: {
marketing: 'marketing@http://localhost:8081/remoteEntry.js',
auth: 'auth@http://localhost:8082/remoteEntry.js',
dashboard: 'dashboard@http://localhost:8083/remoteEntry.js'
},
添加加载微应用的组件:
// container\src\components\DashboardApp.js
import { useEffect, useRef } from 'react'
import { mount } from 'dashboard/DashboardApp'
export default function DashboardApp() {
const ref = useRef()
useEffect(() => {
mount(ref.current)
}, [])
return <div ref={ref}></div>
}
添加懒加载路由配置:
// container\src\App.js
import React, { lazy, Suspense, useState, useEffect } from 'react'
import { Router, Route, Switch } from 'react-router-dom'
import { createBrowserHistory } from 'history'
import Header from './components/Header'
// 懒加载微应用
const MarketingApp = lazy(() => import('./components/MarketingApp'))
const AuthApp = lazy(() => import('./components/AuthApp'))
const DashboardApp = lazy(() => import('./components/DashboardApp'))
const history = createBrowserHistory()
function App() {
// 创建登录状态和设置方法
const [status, setStatus] = useState(false)
useEffect(() => {
// 如果已登录,跳转到 Dashboard
if (status) {
history.push('/dashboard')
}
console.log(status)
}, [status])
return (
<Router history={history}>
<Header status={status} setStatus={setStatus} />
<Suspense fallback={<div>正在加载组件</div>}>
<Switch>
<Route path="/auth/signin">
<AuthApp setStatus={setStatus} />
</Route>
<Route path="/dashboard">
<DashboardApp />
</Route>
<Route path="/">
<MarketingApp />
</Route>
</Switch>
</Suspense>
</Router>
)
}
export default App
重启容器应用。
DashboardApp 路由保护
现在 Dashboard 页面可以随意访问,下面添加登录访问权限,只有登录后才访问。
// container\src\App.js
import { Router, Route, Switch, Redirect } from 'react-router-dom'
<Route path="/dashboard">
{/* 如果未登录,重定向到首页 */}
{!status && <Redirect to="/" />}
<DashboardApp />
</Route>
至此案例完成。