跳到主要内容

shared

2024年03月21日
柏拉文
越努力,越幸运

一、认识


Shared通信,是基于redux实现的适合需要跟踪通信状态通信方式,子应用具备独立运行能力,较为复杂的微前端应用。

二、原理


Shared通信方案的原理就是:主应用基于redux维护一个状态池,通过shared实例暴露一些方法给子应用使用。同时,子应用需要单独维护一份shared实例,在独立运行时使用自身的shared实例,在嵌入主应用时使用主应用的shared实例,这样就可以保证在使用和表现上的一致性。

Shared通信方案需要自行维护状态池,这样会增加项目的复杂度。好处是可以使用市面上比较成熟的状态管理工具,如reduxmobx,可以有更好的状态管理追踪和一些工具集。

Shared通信方案要求父子应用都各自维护一份属于自己的shared实例,同样会增加项目的复杂度。好处是子应用可以完全独立于父应用运行(不依赖状态池),子应用也能以最小的改动被嵌入到其他第三方应用中。

Shared通信方案也可以帮助主应用更好的管控子应用。子应用只可以通过shared实例来操作状态池,可以避免子应用对状态池随意操作引发的一系列问题。主应用的Shared相对于子应用来说是一个黑箱,子应用只需要了解Shared所暴露的API而无需关心实现细节。

三、特点


如果你的应用通信场景较多,希望子应用具备完全独立运行能力,希望主应用能够更好的管理子应用,那么可以考虑Shared通信方案。

3.1 优点

  • 可以自由选择状态管理库,更好的开发体验。 - 比如redux有专门配套的开发工具可以跟踪状态的变化。
  • 子应用无需了解主应用的状态池实现细节,只需要了解shared的函数抽象,实现一套自身的shared甚至空shared即可,可以更好的规范子应用开发。
  • 子应用无法随意污染主应用的状态池,只能通过主应用暴露的shared实例的特定方法操作状态池,从而避免状态池污染产生的问题。
  • 子应用将具备独立运行的能力,Shared通信使得父子应用有了更好的解耦性。

3.2 缺点

  • 主应用需要单独维护一套状态池,会增加维护成本和项目复杂度;
  • 子应用需要单独维护一份shared实例,会增加维护成本;

四、测试


4.1 主应用

  1. 通过vue-cli脚手架工具创建Vue主应用

    vue create main-app
  2. 设置路由模式为hash

    import Vue from 'vue'
    import VueRouter from 'vue-router'
    import Home from '../views/Home'

    Vue.use(VueRouter)

    const routes = [
    {
    name:'Home',
    path:'/',
    component:Home
    }
    ]

    const router = new VueRouter({
    mode: 'history',
    base: process.env.BASE_URL,
    routes
    })

    export default router

  3. 创建vue.config.js配置文件,进行配置:

    a. 配置publicPath

    b. 配置端口号

    module.exports = {
    // hash 模式下可使用
    publicPath: process.env.NODE_ENV === 'development' ? '/' : '/mainApp/',
    devServer: {
    port: 10001,
    },
    }
  4. 布局App.vue,方便之后切换主应用与子应用

    a. 安装element-ui

    npm i element-ui -S

    b. 布局菜单栏与内容区

    ::: warning 细节

    1. 主应用路由容器用 router-view 承载
    2. 微应用容器用 <div id='childAppContainer></div> 承载,且切记微应用容器ID , 之后配置要用的 :::
    <template>
    <div id="app">
    <el-container>
    <el-aside width="200px">
    <el-menu
    :default-active="activeMenu"
    class="el-menu-vertical"
    background-color="#545c64"
    text-color="#fff"
    active-text-color="#ffd04b"
    >
    <router-link to="/">
    <el-menu-item index="/">
    <i class="el-icon-menu"></i><span>首页</span>
    </el-menu-item>
    </router-link>
    <router-link to="/vueChildRouter">
    <el-menu-item index="/vueChildRouter">
    <i class="el-icon-menu"></i><span>Vue子应用</span>
    </el-menu-item>
    </router-link>
    <router-link to="/reactChildRouter">
    <el-menu-item index="/reactChildRouter">
    <i class="el-icon-menu"></i><span>React子应用</span>
    </el-menu-item>
    </router-link>
    </el-menu>
    </el-aside>
    <el-main>
    <!-- 主应用容器 -->
    <router-view></router-view>
    <!-- 子应用容器 -->
    <div id="childAppContainer"></div>
    </el-main>
    </el-container>
    </div>
    </template>

    <script>
    export default{
    name:'VueMainApp',
    data(){
    return {
    currentMenu:'/',
    }
    },
    computed: {
    activeMenu() {
    const route = this.$route
    const { meta, path } = route
    if (meta.activeMenu) {
    return meta.activeMenu
    }
    return path
    },
    }
    }
    </script>

    <style>
    html {
    margin: 0;
    padding: 0;
    height: 100%;
    }
    body {
    margin: 0;
    padding: 0;
    height: 100%;
    }
    a {
    text-decoration: none;
    color: #fff;
    }
    #app {
    width: 100%;
    height: 100%;
    }
    .el-container {
    height: 100%;
    }
    .el-menu-vertical {
    height: 100%;
    }
    .el-menu-item.is-active a {
    color: #ffd04b !important;
    }
    .el-main {
    margin: 0 !important;
    padding: 0 !important;
    }
    </style>

  5. 创建app.js,用来配置子应用

    const apps=[
    {
    name:'vueChilApp',// 必选,微应用的名称,微应用之间必须确保唯一
    entry:'http://localhost:10002',// - 必选,微应用的入口
    container:'#childAppContainer',// -必选,微应用的容器节点的选择器或者 Element 实例
    activeRule:'/vueChildRouter',// - 必选,微应用的激活规则
    props:()=>{return {msg:"Vue 主应用 传给 Vue 子应用的值"}}
    },
    {
    name:'reactChilApp',// 必选,微应用的名称,微应用之间必须确保唯一
    entry:'http://localhost:10003',// - 必选,微应用的入口
    container:'#childAppContainer',// -必选,微应用的容器节点的选择器或者 Element 实例
    activeRule:'/reactChildRouter',// - 必选,微应用的激活规则
    props:()=>{return {msg:"Vue 主应用 传给 React 子应用的值"}}
    },
    ]
    export default apps;
  6. 创建qiankun.config.js,用来配置QianKun

    a. 安装qiankun依赖

    npm i qiankun -S

    b. 配置qiankun.config.js文件

    import { registerMicroApps,  start } from 'qiankun';

    import apps from './app';

    /**
    * @description: 注册子应用
    */
    function registerApps() {
    registerMicroApps(
    apps,
    {
    beforeLoad: [
    loadApp => {
    console.log('vue 主应用 正在加载的子应用', loadApp);
    },
    ],
    beforeMount: [
    mountApp => {
    console.log('vue 主应用 正在挂载的子应用', mountApp);
    },
    ],
    afterMount: [
    mountApp => {
    console.log('vue 主应用 挂载完成的子应用', mountApp);
    },
    ],
    afterUnmount: [
    unloadApp => {
    console.log('vue 主应用 卸载完成的子应用', unloadApp);
    },
    ],
    },
    );
    start({
    // prefetch: true, // 可选,是否开启预加载,默认为 true。
    // sandbox: true, // 可选,是否开启沙箱,默认为 true。//从而确保微应用的样式不会对全局造成影响。
    // singular: true, // 可选,是否为单实例场景,单实例指的是同一时间只会渲染一个微应用。默认为 true。
    // fetch: () => {}, // 可选,自定义的 fetch 方法。
    // getPublicPath: (url) => { console.log(url); },
    // getTemplate: (tpl) => { console.log(tpl); },
    // excludeAssetFilter: (assetUrl) => { console.log(assetUrl); }, // 可选,指定部分特殊的动态加载的微应用资源(css/js) 不被qiankun 劫持处理
    });
    }

    export default registerApps;
  7. 配置main.js入口文件

    ::: warning 细节

    1. 注册乾坤时需要等到主应用DOM加载完成后,所以需要用nextTick包裹 :::
    import Vue from 'vue'
    import App from './App.vue'
    import router from './router'
    import store from './store'
    import ElementUI from 'element-ui'
    import 'element-ui/lib/theme-chalk/index.css'
    import registerApps from './qiankun.config'

    Vue.use(ElementUI);
    Vue.config.productionTip = false

    const MainApp=new Vue({
    router,
    store,
    render: h => h(App)
    }).$mount('#app')


    /**
    * @description: 确保装载子应用的容器已创建,我们需要在 new Vue()之后,等DOM加载好了在注册并启动qiankun
    */
    MainApp.$nextTick(()=>{
    registerApps();
    });
  8. 通过redux,实现主应用shared通信

    a. 安装redux

    yarn add redux -S 

    b. 主应用中创建store用于管理全局状态池

    import { createStore } from "redux";

    const reducer = (state={}, action)=> {
    switch (action.type) {
    default:
    return state;
    // 设置 Token
    case "SET_TOKEN":
    return {
    ...state,
    token: action.payload,
    };
    }
    };

    const store = createStore(reducer);
    export default store;

    c. 实现主应用shared实例

    import store from "./store";

    class Shared {
    /**
    * 获取 Token
    */
    getToken() {
    const state = store.getState();
    return state.token || "";
    }

    /**
    * 设置 Token
    */
    setToken(token) {
    // 将 token 的值记录在 store 中
    store.dispatch({
    type: "SET_TOKEN",
    payload: token
    });
    }
    }

    const shared = new Shared();
    export default shared;

    d. 通过Vue组件调用shared实例setTokengetToken

    <template>
    <div class="home">
    <h1>QianKun主应用 Home 页面</h1>
    <button @click='communication'>主应用通信</button>
    </div>
    </template>

    <script>
    import shared from "../shared"
    export default {
    name: 'Home',
    methods:{
    communication(){
    shared.setToken('123456+主应用');
    }
    }
    }
    </script>

    e. 将shared实例通过props传递给子应用

    import shared from '@/shared'
    const apps=[
    {
    name:'vueChilApp',// 必选,微应用的名称,微应用之间必须确保唯一
    entry:'http://localhost:10002',// - 必选,微应用的入口
    container:'#childAppContainer',// -必选,微应用的容器节点的选择器或者 Element 实例
    activeRule:'/vueChildRouter',// - 必选,微应用的激活规则
    props:()=>{return {msg:"Vue 主应用 传给 Vue 子应用的值",shared}}
    },
    {
    name:'reactChilApp',// 必选,微应用的名称,微应用之间必须确保唯一
    entry:'http://localhost:10003',// - 必选,微应用的入口
    container:'#childAppContainer',// -必选,微应用的容器节点的选择器或者 Element 实例
    activeRule:'/reactChildRouter',// - 必选,微应用的激活规则
    props:()=>{return {msg:"Vue 主应用 传给 React 子应用的值",shared}}
    },
    ]
    export default apps;

4.2 Vue 子应用

  1. 通过vue-cli脚手架搭建Vue子应用

    vue create vue-chil-app
  2. 设置路由模式为hash

    import Vue from 'vue'
    import VueRouter from 'vue-router'
    import Home from '../views/Home.vue'

    Vue.use(VueRouter)

    const routes = [
    {
    path: '/',
    name: 'Home',
    component: Home
    },
    {
    path: '/about',
    name: 'About',
    component: () => import('../views/About.vue')
    }
    ]

    /**
    * @description: 配置路由
    * * base: 路由采用 hash 模式,所以必须配置 base 选项
    * * mode: 路由采用 hash 模式
    */
    const router = new VueRouter({
    base:window.__POWERED_BY_QIANKUN__?'/vueChildRouter':process.env.BASE_URL,
    mode:'history',
    routes
    })

    export default router
  3. 创建vue.config.js配置文件,进行配置:

    a. 配置访问路径publicPath

    a. 配置端口号port

    b. 配置跨域headers

    c. 配置打包类型output

    module.exports = {
    /**
    * @description: 路由采用 hash 模式,需要配置 publicPath 访问路径
    */
    publicPath: process.env.NODE_ENV === 'development' ? '/' : '/vueChildRouter/',
    /**
    * @description: 配置端口号、配置允许本地跨域
    * * 配置端口号: 主应用端口号为 10000 , 且主应用中已配置了子应用端口号为 10001 ,所以要保持端口号一致
    * * 允许跨域: 本地开发中,端口号不一致,所以要允许跨域
    */
    devServer: {
    port: 10002,
    headers: {
    'Access-Control-Allow-Origin': '*'
    }
    },
    configureWebpack: {
    /**
    * @description: 配置打包文件类型为 umd
    * * library: 微应用包名
    * * libraryTarget: 将你的 library 暴露为所有的模块定义下都可运行的方式
    * * jsonpFunction: 按需加载相关,设置为 webpackJsonp_vueChildApp 即可
    */
    output: {
    library: `vueChildApp`,
    libraryTarget: 'umd',
    jsonpFunction: `webpackJsonp_vueChildApp`
    }
    }
    }
  4. 创建public-path.js文件

    if (window.__POWERED_BY_QIANKUN__) {
    // eslint-disable-next-line no-undef
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
    }
  5. 配置main.js入口文件

    import Vue from 'vue'
    import App from './App.vue'
    import router from './router'
    import store from './store'
    import './public-path'

    Vue.config.productionTip = false
    let instance = null;

    /**
    * @description: 渲染函数
    * 1. router 路由: 采用 hash 模式
    * 2. container 容器: #vueChildApp 为 html 模板中挂在应用的 ID ,必须一致且唯一,否则子项目无法独立运行
    */
    function render() {
    instance = new Vue({
    router,
    store,
    render: h => h(App),
    }).$mount('#vueChildApp');
    }

    /**
    * @description: 处理非 QianKun 环境下的渲染,保证独立运行完整
    */
    if (!window.__POWERED_BY_QIANKUN__) {
    render()
    }

    /**
    * @description: QianKun bootstrap 生命周期
    * *特点: bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap
    * *作用: 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等
    */
    export async function bootstrap () {
    console.log('vue 子应用 bootstrap 阶段')
    }

    /**
    * @description: QianKun mount 生命周期
    * *特点: 应用每次进入都会调用 mount 方法
    * *作用: 通常我们在这里触发应用的渲染方法
    */
    export async function mount (props) {
    console.log('vue 子应用 mount 阶段')
    console.log('vue 子应用 mount 通信',props);
    render(props)
    }

    /**
    * @description: QianKun unmount 生命周期
    * *特点: 应用每次 切出/卸载 会调用的方法
    * *作用: 通常在这里我们会卸载微应用的应用实例
    */
    export async function unmount () {
    console.log('vue 子应用 unmount 阶段')
    instance.$destroy()
    instance.$el.innerHTML = ''
    instance = null
    }
  6. 修改index.html

    注意
    1. 修改public>index.html 挂载应用的ID,否则子应用无法独立运行
    <!DOCTYPE html>
    <html lang="">
    <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">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title><%= htmlWebpackPlugin.options.title %></title>
    </head>
    <body>
    <noscript>
    <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="vueChildApp"></div>
    <!-- built files will be auto injected -->
    </body>
    </html>
  7. 实现子应用的shared通信

    a. 实现子应用自身的shared类以及管理shared类的sharedModule

    注意

    Shared类和SharedModule类的用处:

    • Shared类: 子应用自身的shared,子应用独立运行时将使用该shared,子应用的shared使用localStorage来操作token
    • SharedModule: 用于管理shared,例如重载shared实例、获取shared实例等等;
    class Shared {
    /**
    * 获取 Token
    */
    getToken() {
    // 子应用独立运行时,在 localStorage 中获取 token
    return localStorage.getItem("token") || "";
    }

    /**
    * 设置 Token
    */
    setToken(token) {
    // 子应用独立运行时,在 localStorage 中设置 token
    localStorage.setItem("token", token);
    }
    }

    class SharedModule {
    static shared = new Shared();
    /**
    * 重载 shared
    */
    static overloadShared(shared) {
    SharedModule.shared = shared;
    }

    /**
    * 获取 shared 实例
    */
    static getShared() {
    return SharedModule.shared;
    }
    }

    export default SharedModule;

    b. 子应用入口注入shared

    注意
    1. 子应用独立运行或者在主应用的生命周期钩子中运行时shared是怎么管理的? 答: 当主应用挂载子应用时,调用生命周期render,此时主应用会传给子应用props。如果传入的shared为空时,使用子应用自身的shared;如果传入的shared不为空时,主应用传入的shred将会重载子应用的shared
    import Vue from "vue";
    import App from "./App.vue";
    import router from "./router";
    import store from "./store";
    import "./public-path";
    import SharedModule from '@/shared'

    Vue.config.productionTip = false;
    let instance = null;

    /**
    * @description: 渲染函数
    * 1. router 路由: 采用 hash 模式
    * 2. container 容器: #vueChildApp 为 html 模板中挂在应用的 ID ,必须一致且唯一,否则子项目无法独立运行
    */
    function render(props = {}) {
    /**
    * @description: shared 的两种情况
    * a. 当传入的 shared 为空时,使用子应用自身的 shared
    * b. 当传入的 shared 不为空时,主应用传入的 shared 将会重载子应用的 shared
    */
    const { shared = SharedModule.getShared() } = props;
    SharedModule.overloadShared(shared);
    instance = new Vue({
    router,
    store,
    render: (h) => h(App),
    }).$mount("#vueChildApp");
    }

    /**
    * @description: 处理非 QianKun 环境下的渲染,保证独立运行完整
    */
    if (!window.__POWERED_BY_QIANKUN__) {
    render();
    }

    /**
    * @description: QianKun bootstrap 生命周期
    * *特点: bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap
    * *作用: 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等
    */
    export async function bootstrap() {
    console.log("vue 子应用 bootstrap 阶段");
    }

    /**
    * @description: QianKun mount 生命周期
    * *特点: 应用每次进入都会调用 mount 方法
    * *作用: 通常我们在这里触发应用的渲染方法
    */
    export async function mount(props) {
    console.log("vue 子应用 mount 阶段");
    console.log("vue 子应用 mount 通信", props);
    render(props);
    }

    /**
    * @description: QianKun unmount 生命周期
    * *特点: 应用每次 切出/卸载 会调用的方法
    * *作用: 通常在这里我们会卸载微应用的应用实例
    */
    export async function unmount() {
    console.log("vue 子应用 unmount 阶段");
    instance.$destroy();
    instance.$el.innerHTML = "";
    instance = null;
    }

    c. 在Vue组件中调用shared实例用来管理token

    <template>
    <div id="app">
    <h1>QianKun Vue 子应用 首页</h1>
    </div>
    </template>

    <script>
    import SharedModule from '@/shared'
    export default{
    name:'VueChildApp',
    mounted(){
    const shared=SharedModule.getShared();
    const token=shared.getToken();
    console.log(token);
    }
    }
    </script>

4.3 React 子应用

  1. 通过create-react-app脚手架搭建React子应用
npx create-react-app react-child-app
  1. 通过react-app-rewired配置Webpack

    a. 安装react-app-rewired

    yarn add react-app-rewired -S

    b. 创建config-overrides.js

    module.exports = {
    /**
    * @description: 配置打包文件类型为 umd
    * * library: 微应用包名
    * * libraryTarget: 将你的 library 暴露为所有的模块定义下都可运行的方式
    * * jsonpFunction: 按需加载相关,设置为 webpackJsonp_vueChildApp 即可
    */
    webpack: (config) => {
    config.output.library = 'reactChildApp'
    config.output.libraryTarget = 'umd'
    config.output.jsonpFunction = `webpackJsonp_reactChildApp`
    config.output.publicPath = process.env.NODE_ENV === 'development' ? '/' : '/reactRouter/';
    return config
    },
    /**
    * @description: 配置允许本地跨域
    * * 允许跨域: 本地开发中,端口号不一致,所以要允许跨域
    */
    devServer: (configFunction) => {
    return function(proxy, allowedHost) {
    const config = configFunction(proxy, allowedHost)
    config.headers = {
    'Access-Control-Allow-Origin': '*'
    }
    config.open = false;
    return config
    }
    }
    }

    c. 配置服务命令 package.json

    {
    "name": "react-child-app",
    "version": "0.1.0",
    "private": true,
    "dependencies": {
    "cra-template": "1.1.2",
    "react": "^17.0.2",
    "react-app-rewired": "^2.1.8",
    "react-dom": "^17.0.2",
    "react-scripts": "4.0.3"
    },
    "scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build"
    },
    "eslintConfig": {
    "extends": [
    "react-app",
    "react-app/jest"
    ]
    },
    "browserslist": {
    "production": [
    ">0.2%",
    "not dead",
    "not op_mini all"
    ],
    "development": [
    "last 1 chrome version",
    "last 1 firefox version",
    "last 1 safari version"
    ]
    }
    }
  2. 创建.env文件,修改端口

    PORT=10003
    WDS_SOCKET_PORT=10003

    :::

  3. 创建public-path.js文件

    if (window.__POWERED_BY_QIANKUN__) {
    // eslint-disable-next-line
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
    //__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
    }
  4. 配置index.js入口文件

    import React from 'react';
    import ReactDOM from 'react-dom';
    import './index.css';
    import App from './App';
    import "./public-path";


    /**
    * @description: 渲染函数
    * 1. container 容器: #vueChildApp 为 html 模板中挂在应用的 ID ,必须一致且唯一,否则子项目无法独立运行
    */
    function render(){
    ReactDOM.render(<App />,document.getElementById('reactChildApp'));
    }

    /**
    * @description: 处理非 QianKun 环境下的渲染,保证独立运行完整
    */
    if (!window.__POWERED_BY_QIANKUN__) {
    render();
    }

    /**
    * @description: QianKun bootstrap 生命周期
    * *特点: bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap
    * *作用: 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等
    */
    export async function bootstrap() {
    console.log("ReactMicroApp bootstraped");
    }

    /**
    * @description: QianKun mount 生命周期
    * *特点: 应用每次进入都会调用 mount 方法
    * *作用: 通常我们在这里触发应用的渲染方法
    */
    export async function mount(props) {
    console.log("ReactMicroApp mount", props);
    render(props);
    }

    /**
    * @description: QianKun unmount 生命周期
    * *特点: 应用每次 切出/卸载 会调用的方法
    * *作用: 通常在这里我们会卸载微应用的应用实例
    */
    export async function unmount() {
    console.log("ReactMicroApp unmount");
    ReactDOM.unmountComponentAtNode(document.getElementById("reactChildApp"));
    }
  5. 修改index.html

    ::: warning

    1. 修改public>index.html挂载应用的ID,否则子应用无法独立运行 :::
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
    name="description"
    content="Web site created using create-react-app"
    />
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
    <!--
    manifest.json provides metadata used when your web app is installed on a
    user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
    -->
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <!--
    Notice the use of %PUBLIC_URL% in the tags above.
    It will be replaced with the URL of the `public` folder during the build.
    Only files inside the `public` folder can be referenced from the HTML.

    Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
    work correctly both with client-side routing and a non-root public URL.
    Learn how to configure a non-root public URL by running `npm run build`.
    -->
    <title>React App</title>
    </head>
    <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="reactChildApp"></div>
    <!--
    This HTML file is a template.
    If you open it directly in the browser, you will see an empty page.

    You can add webfonts, meta tags, or analytics to this file.
    The build step will place the bundled scripts into the <body> tag.

    To begin the development, run `npm start` or `yarn start`.
    To create a production bundle, use `npm run build` or `yarn build`.
    -->
    </body>
    </html>
  6. 实现子应用的shared通信

    a. 实现子应用自身的shared类以及管理shared类的sharedModule

    细节

    Shared类和SharedModule类的用处:

    • Shared类: 子应用自身的shared,子应用独立运行时将使用该shared,子应用的shared使用localStorage来操作token
    • SharedModule: 用于管理shared,例如重载shared实例、获取shared实例等等;
    react-child-app/src/shared/index.js
    class Shared {
    /**
    * 获取 Token
    */
    getToken() {
    // 子应用独立运行时,在 localStorage 中获取 token
    return localStorage.getItem("token") || "";
    }

    /**
    * 设置 Token
    */
    setToken(token) {
    // 子应用独立运行时,在 localStorage 中设置 token
    localStorage.setItem("token", token);
    }
    }

    class SharedModule {
    static shared = new Shared();
    /**
    * 重载 shared
    */
    static overloadShared(shared) {
    SharedModule.shared = shared;
    }

    /**
    * 获取 shared 实例
    */
    static getShared() {
    return SharedModule.shared;
    }
    }

    export default SharedModule;

    b. 子应用入口注入shared

    注意
    1. 子应用独立运行或者在主应用的生命周期钩子中运行时shared是怎么管理的? 答: 当主应用挂载子应用时,调用生命周期render,此时主应用会传给子应用props。如果传入的shared为空时,使用子应用自身的shared;如果传入的shared不为空时,主应用传入的shred将会重载子应用的shared
    react-child-app/src/index.js
    import React from 'react';
    import ReactDOM from 'react-dom';
    import './index.css';
    import App from './App';
    import "./public-path";
    import SharedModule from './shared/index';

    /**
    * @description: 渲染函数
    * 1. container 容器: #vueChildApp 为 html 模板中挂在应用的 ID ,必须一致且唯一,否则子项目无法独立运行
    */
    function render(props={}){
    /**
    * @description: shared 的两种情况
    * a. 当传入的 shared 为空时,使用子应用自身的 shared
    * b. 当传入的 shared 不为空时,主应用传入的 shared 将会重载子应用的 shared
    */
    const { shared = SharedModule.getShared() } = props;
    SharedModule.overloadShared(shared);
    ReactDOM.render(<App />,document.getElementById('reactChildApp'));
    }

    /**
    * @description: 处理非 QianKun 环境下的渲染,保证独立运行完整
    */
    if (!window.__POWERED_BY_QIANKUN__) {
    render();
    }

    /**
    * @description: QianKun bootstrap 生命周期
    * *特点: bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap
    * *作用: 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等
    */
    export async function bootstrap() {
    console.log("ReactMicroApp bootstraped");
    }

    /**
    * @description: QianKun mount 生命周期
    * *特点: 应用每次进入都会调用 mount 方法
    * *作用: 通常我们在这里触发应用的渲染方法
    */
    export async function mount(props) {
    console.log("ReactMicroApp mount", props);
    render(props);
    }

    /**
    * @description: QianKun unmount 生命周期
    * *特点: 应用每次 切出/卸载 会调用的方法
    * *作用: 通常在这里我们会卸载微应用的应用实例
    */
    export async function unmount() {
    console.log("ReactMicroApp unmount");
    ReactDOM.unmountComponentAtNode(document.getElementById("reactChildApp"));
    }

    c. 在React组件中调用shared实例用来管理token

    react-child-app/src/App.js
    import './App.css';
    import {useEffect} from 'react'
    import SharedModule from './shared';

    function App() {
    useEffect(()=>{
    const shared=SharedModule.getShared();
    const token=shared.getToken();
    console.log(token);
    });
    return (
    <div className="App">
    <h1>QianKun React 子应用 首页</h1>
    </div>
    );
    }

    export default App;

参考资料