参考文档
相关库的版本
- 主应用技术栈(create-react-app构建):
- react@18.2.0
- react-router-dom@6.10.0
- react-redux@8.0.5
- qiankun@2.10.11
- "@craco/craco
- 子应用1技术栈(create-react-app构建)
- react@18.2.0
- react-router-dom@6.10.0
- react-redux@8.0.5
- "@craco/craco
- 子应用2技术栈(vue-cli构建的):
- vue@2.6.10
- vue-router@3.1.3
- vuex@3.0.1
主应用的配置
- 下载依赖 yarn add qiankun/npm i qiankun,
- 主应用路由配置 我的子应用容器是QiankunPage组件
import React, { lazy } from 'react'
import { Navigate } from 'react-router-dom'
import NotExistPage from '@/pages/404'
import QiankunPage from '@/pages/QiankunPage'
import AuthRouter from '@/components/AuthRouter'
const Layout = lazy(() => import("../pages/Layout"))
const Mainpage = lazy(() => import("../pages/MainPage"))
const routes = [
{
path: '/',
element: <Layout />,
children: [
{
path: 'mainpage',
element: <Mainpage />
},
{
path: '/*',
element: <QiankunPage />
},
]
},
{
path: '/404',
element: <NotExistPage />
},
]
export default routes
- QiankunPage组件相关代码如下:
import { useEffect } from 'react'
import { Spin } from 'antd'
import { registerApps } from '../../qiankun'
import { connect } from 'react-redux'
import globalAction from "@/qiankun/globalAction"
import { setGlobalState } from '@/qiankun/reducer/actions'
const QiankunPage = (props) => {
useEffect(() => {
//注册子应用
registerApps()
//监听由乾坤管理的状态
globalAction.onGlobalStateChange((state, prev) => {
props.dispatch(setGlobalState(state)) //乾坤的管理的state存到react-redux的store方便使用
})
}, [])
return (<Spin spinning={props.loading}>
// 俺的子应用容器节点
<div id="qiankun_container" style={{ flex: 1, height: '100%', }} />
</Spin>)
}
export default connect(({ qiankunGlobalState }) => {
return {
loading: qiankunGlobalState.loading[qiankunGlobalState.activeAppName]
}
})(QiankunPage)
- 注册子应用
//子应用配置文件:subAppConfig.js文件
import globalAction from "@/qiankun/globalAction"
const apps = [
{
name: 'sub_react', //微应用的名称
entry: 'http://localhost:5000/', //微应用的入口
container: '#qiankun_container', //微应用的容器节点的选择器或者 Element 实例
activeRule: ({ href }) => {
return isActiveCurrentApp("sub_react", href, "/sub_react/")
}, //微应用的激活规则--当浏览器 url 发生变化时,会自动检查每一个微应用注册的 activeRule 规则,符合规则的应用将会被自动激活。
props: { //props属性用来给子应用传递下发消息
//下发给子应用的全局状态
getGlobalState: globalAction.getGlobalState
}
},
{
name: 'sub_vue', //微应用的名称
entry: 'http://localhost:8002/', //微应用的入口
container: '#qiankun_container', //微应用的容器节点的选择器或者 Element 实例
activeRule: ({ href }) => {
return isActiveCurrentApp("sub_vue", href, "/sub_vue/")
}, //微应用的激活规则--当浏览器 url 发生变化时,会自动检查每一个微应用注册的 activeRule 规则,符合规则的应用将会被自动激活。
props: {
getGlobalState: globalAction.getGlobalState
}
}
]
//判断是否激活当前应用
const isActiveCurrentApp = (appName, href, urlShort) => {
const flag = href.includes(urlShort)
if (flag) {
//此处把当前激活的app存起来,方便在app资源下载阶段开启加载动画,避免白屏时间过长
globalAction.setGlobalState({ activeAppName: appName })
}
return flag
}
export default apps
import { registerMicroApps, start, addGlobalUncaughtErrorHandler } from 'qiankun'
import subApps from './subAppConfig'
import globalAction from "@/qiankun/globalAction"
/* 调用registerMicroApps注册子应用 */
const registerApps = () => {
let apps = subApps
registerMicroApps(apps, {
beforeLoad: [
app => {
console.log(`${app.name}load前完成了 %c%s`, 'color: green;', app.name)
}
],
beforeMount: [
app => {
console.log(`${app.name}挂载前完成了 %c%s`, 'color: green;', app.name)
}
],
afterMount: [
app => {
const loading = { ...globalAction.getGlobalState().loading, [app.name]: false }
globalAction.setGlobalState({ loading })
console.log(`${app.name}应用挂载完成了 %c%s`, 'color: green;', app.name)
}
],
afterUnmount: [
app => {
console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name)
}
]
})
// 启动qiankun
start({
prefetch: true, //并开启预加载
sandbox: {
strictStyleIsolation: true, // 开启样式隔离
},
})
}
addGlobalUncaughtErrorHandler(event => {
const { message: msg } = event
if (msg && msg.includes('died in status LOADING_SOURCE_CODE')) {
console.error('子应用加载失败,请检查应用是否可运行')
}
})
export { registerApps }
- 创建qiankun状态管理页面(为了完成应用之间的通信)
import { initGlobalState } from 'qiankun'
import store from "@/store"
import { setGlobalState } from "./reducer/actions"
const initialState = {
loading: {
'sub_react': true,// 加载动画初始化为true
"sub_vue": true
},
activeAppName: '',
message: '全局消息'
}
store.dispatch(setGlobalState(initialState))
const globalAction = initGlobalState(initialState)
// 获取state的方法
globalAction.getGlobalState = key => {
return key ? initialState[key] : initialState
}
export default globalAction
到此主应用里面的工作已经完成了
子应用配置
集成react子应用
- 在src底下创建public-path.js文件夹 写入如下代码
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
- craco.config.js文件配置
const path = require('path')
const { name } = require('./package')
module.exports = {
webpack: {
// 路径别名
alias: {
'@': path.resolve(__dirname, 'src')
},
configure: (webpackConfig, { env, paths }) => {
webpackConfig.output.library = `${name}-[name]`
webpackConfig.output.libraryTarget = 'umd'
webpackConfig.output.globalObject = 'window'
// webpackConfig.output.jsonpFunction = `webpackJsonp_${name}`;
return webpackConfig
}
},
// babel 配置
babel: {},
plugins: [],
devServer: {
port: 5000,
headers: {
'Access-Control-Allow-Origin': '*'
}
}
}
- 在index.js入口文件中做如下工作
import React from 'react'
import ReactDOM from 'react-dom/client'
import { RouterProvider } from 'react-router-dom'
import { Provider } from 'react-redux'
import store from '@/store'
import { ConfigProvider } from 'antd'
import zhCN from 'antd/es/locale/zh_CN'
import './index.css'
import routes from './router'
import { StyleProvider, legacyLogicalPropertiesTransformer } from '@ant-design/cssinjs'
import '@/utils/importDayjsPlugin'
import './public-path'; // 引入qiankun配置文件
import globalAction from "@/utils/globalAction"
let root = null
//获取ReactDOMRoot实例
const getReactDOMRoot = (props) => {
if (window.__POWERED_BY_QIANKUN__) {
root = ReactDOM.createRoot(props.container.querySelector('#root'))
globalAction.setActions(props)
} else {
root = ReactDOM.createRoot(document.querySelector('#root'))
}
return root
}
function render (props) {
getReactDOMRoot(props).render(
<ConfigProvider locale={zhCN}>
<StyleProvider hashPriority="high" transformers={[legacyLogicalPropertiesTransformer]}>
<Provider store={store}>
<RouterProvider router={routes} />
</Provider>
</StyleProvider>
</ConfigProvider>
)
}
if (!window.__POWERED_BY_QIANKUN__) {
render({})
}
/**
* bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
*/
export async function bootstrap (props) {
console.log('%c[react18] app bootstraped', 'background:#364a93;color:#fff', props)
}
/**
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
*/
export async function mount (props) {
console.log('%c[react18] props from11111 main framework', 'background:#364a93;color:#fff', props)
render(props)
}
/**
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
*/
export async function unmount (props) {
console.log('%c[react18] app unmount', 'background:#364a93;color:#fff', props)
root.unmount()
}
- 路由配置(我用的hash路由,用history也是可以的,但是主子应用的路由模式得一制)
import React from 'react'也是可以的,但是主子应用的路由模式得一制
import { createHashRouter } from 'react-router-dom'
import Layout from '../pages/Layout'
import NotExistPage from '@/pages/404'
import AuthRouter from '@/components/AuthRouter'
import Qiankun from "../pages/Qiankun"
import Page1 from "@/pages/Page1"
import Page2 from "@/pages/Page2"
const childRoutes = [
{
path: '/sub_react/page1',
element: (
<AuthRouter>
<Page1 />
</AuthRouter>
)
},
{
path: '/sub_react/page2',
element: (
<AuthRouter>
<Page2 />
</AuthRouter>
)
}
]
let routes = [
{
path: '/',
//我的子应用也需要单独做为项目使用所以这里做了区分,Qiankun组件做了一下主要用于监听主应用的消息并映射到store里面
element: !window.__POWERED_BY_QIANKUN__ ? (
<AuthRouter>
<Layout />
</AuthRouter>
) : <Qiankun />,
children: childRoutes
},
{
path: '/*',
element: <NotExistPage />
}
]
export default createHashRouter(routes)
- Qiankun.jsx 代码. 如下:
import React, { useEffect } from 'react'
import { connect } from 'react-redux'
import { Outlet } from 'react-router-dom'
import globalAction from "@/utils/globalAction"
import { setGlobalState } from "./globalReducer/actions"
import "./index.less"
const Qiankun = props => {
useEffect(() => {
globalAction.onGlobalStateChange((state) => {
props.dispatch(setGlobalState(state))
}, true)
}, [])
return <div className="sub_qiankun_container"> <Outlet /></div>
}
export default connect(() => ({}))(Qiankun)
- globalAction.js代码如下:
class globalAction {
actions = {
onGlobalStateChange: () => { },
setGlobalState: () => { }
};
globalState = {}
// 设置actions
setActions (actions) {
this.actions = actions;
}
// 映射 onGlobalStateChange
onGlobalStateChange (...args) {
return this.actions.onGlobalStateChange && this.actions.onGlobalStateChange(...args);
}
//获取主应用管理的所有状态
getGlobalState (...args) {
return this.actions.getGlobalState && this.actions.getGlobalState(...args);
}
// 映射 setGlobalState
setGlobalState (...args) {
return this.actions.setGlobalState(...args);
}
}
export default new globalAction()
#####集成vue子应用
- 在src底下创建public-path.js文件夹 写入如下代码
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
- vue.config.js必要的配置
devServer: {
hot: true,
disableHostCheck: true,
port,
overlay: {
warnings: false,
errors: true,
},
headers: {
'Access-Control-Allow-Origin': '*',
},
},
configureWebpack: {
resolve: {
alias: {
'@': resolve('src'),
},
},
output: {
// 把子应用打包成 umd 库格式
library: `${name}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${name}`,
},
},
- 在 main.js入口文件的改造
import './public-path';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import Vue from 'vue';
import VueRouter from 'vue-router';
import App from './App.vue';
import routes from './router';
import store from './store';
import "@/assets/fonts/iconfont.css"
import "@/assets/css/global.css"
Vue.config.productionTip = false;
Vue.use(ElementUI);
let router = null;
let instance = null;
function render (props = {}) {
const { container } = props;
router = new VueRouter({
base: "",
mode: 'hash',
routes,
});
instance = new Vue({
router,
store,
render: h => h(App),
}).$mount(container ? container.querySelector('#app') : '#app');
}
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
export async function bootstrap () {
console.log('[vue] vue app bootstraped');
}
export async function mount (props) {
console.log('[vue] mount props from main framework', props);
render(props);
}
export async function unmount () {
instance.$destroy();
instance.$el.innerHTML = '';
instance = null;
router = null;
}
- 路由配置(我用的hash路由,用history也是可以的,但是主子应用的路由模式得一制)
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter);
const routes = [
{
path: "/",
redirect: "/sub_vue/1111"
},
{
path: '/sub_vue/page1', //
name: 'sub_vue_1',
component: () => import(/* webpackChunkName: "page1" */ '../views/demo1/index.vue'),
},
{
path: '/sub_vue/page2', //我的日程
name: 'sub_vue_2',
component: () => import(/* webpackChunkName: "page2" */ '../views/demo/index.vue'),
},
];
export default routes;