微前端(qiankun)之应用间的通信方案-发布订阅

写在开头

微前端,现在也算是一个老生常谈的问题了。微前端的概念来源于微服务的架构理念,其核心在于将一个庞大的前端应用拆分成多个独立灵活的小型应用,它于2016年提出,主要为了解决 iframe 的各种潜在问题。✅

在当今的前端开发领域,随着业务复杂度的不断攀升以及团队规模的持续扩大,单体式前端应用架构逐渐暴露出诸多弊端。微前端架构应运而生,它旨在将一个大型的前端应用拆分成多个可独立开发、部署、运行的小型应用,这些小型应用能够协同工作,共同构建出功能丰富、体验流畅的完整系统。

当前主流的微前端方案如下:

  • iframe:Web中的原生方案,也是微前端的起源,接入很简单,但是最大缺点就是通信很麻烦。
  • single-spa:较早兴起的微前端框架,是后续很多微前端框架的奠基石。
  • qiankun:基于 single-spa,由阿里巴巴出品,国内比较主流的微前端方案,社区也很活跃,值得入手使用。
  • micro-app:基于 Webcomponent 的微前端方案,由京东出品,不久前刚刚发布了 v1.0 版本。不久前,小编刚好在掘金上看了官方发布的文章介绍,可以看看,传送门
  • EMP:基于 webpack 5 module federation(模块联邦) 的微前端方案,由欢聚时代出品。
  • 无界:腾讯推出的一款微前端解决方式。它是一种基于 Web Components + iframe 的全新微前端方案,继承 iframe 的优点,补足 iframe 的缺点,让 iframe 焕发新生。

本次要分享的是关于微前端的 qiankun 方案相关的内容,请诸君按需食用哈。

先贴个官方文档,内容不多,自行浏览一遍哈:传送门。🚀🚀🚀

主应用

主应用在 qiankun 架构中扮演着 "容器" 与 "协调者" 的角色,负责整合、调度各个子应用资源,把控整体页面布局与路由逻辑。

这里小编以 Vue3 作为主应用框架的项目为例,先来初始化项目:

js 复制代码
npm create vite@latest

小编选择的是 Vue3+TS 形式。

安装路由:

js 复制代码
npm i vue-router -S

新建 router/index.ts 文件:

js 复制代码
import { createRouter, createWebHistory } from 'vue-router';

const router = createRouter({
    // 必须是history模式
    history: createWebHistory(),
    routes: [],
})

export default router;

qiankun 微前端框架中,主应用的路由模式选择会影响到子应用的加载和路由整合。虽然没有严格规定主应用不能使用 createHashHistory 模式,但在一些场景下, createWebHistory 模式更适合与 qiankun 配合使用。

你也不想你的应用路径是这样子吧,/#/main/#/yd-vue2,看着就挺奇怪。🤡

路由与布局你可以根据自己的实际需求场景再进行扩展,这里就不多阐述啦。

主应用还必须安装 qiankun

js 复制代码
npm i qiankun -S

进行子应用的注册,main.ts 文件:

js 复制代码
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
// 引入qiankun
import { registerMicroApps, start } from 'qiankun';
// 为了样式好看一点,使用element-plus,你可以自行选择
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';

// 进行子应用的注册
registerMicroApps([
    {
        name: 'yd-vue2', // 取自子应用 package.json 文件中的name属性,必填
        entry: '//localhost:3001', // 子应用端口
        container: '#yd-container', // 子应用挂载的dom
        activeRule: '/yd-vue2', // 子应用的路由入口
        props: {} // 传给子应用的数据,可以传递一些主应用初始化全局性的数据,比如语言、全局配置,其他数据的传递后续咱们使用发布订阅的形式来完成
    },
    {
        name: 'yd-react',
        entry: '//localhost:3002',
        container: '#yd-container',
        activeRule: '/yd-react',
        props: {}
    },
    {
        name: 'yd-vue3',
        entry: '//localhost:3003',
        container: '#yd-container',
        activeRule: '/yd-vue3',
        props: {}
    }
]);

start({
    sandbox: { 
        //样式隔离
        experimentalStyleIsolation: true
    }
});

const app = createApp(App);

app.use(ElementPlus);
app.use(router);
app.mount('#app');

(如果你的子应用服务一开始还没有启动,可以先把子应用的注册逻辑注释了,等子应用一个一个启动再放开,否则,可能会有报错。🙈)

关于 start(opts?)opts 参数情况可以自行看看官方文档描述,没几个配置项,自行过一遍就行啦。😋

App.vue 文件:

js 复制代码
<template>
    <el-menu mode="horizontal">
        <el-menu-item index="1"><router-link to="/yd-vue2">子应用(vue2)</router-link></el-menu-item>
        <el-menu-item index="2"><router-link to="/yd-react">子应用(react)</router-link></el-menu-item>
        <el-menu-item index="3"><router-link to="/yd-vue3">子应用(vue3)</router-link></el-menu-item>
    </el-menu>
    <h1>以下为子应用内容:</h1>
    <div id="yd-container"></div>
</template>

这样子,主应用基本就完成了,接下来就需要将子应用给接入进来。

子应用

子应用的接入其实官网都有详细的介绍,可以自行先瞅瞅:传送门

小编接下来会逐步介绍接入 Vue2React18Vue3 的具体步骤,你可以做一个参考。😁

Vue2

初始化项目:

js 复制代码
vue create your-project-name

修改 vue.config.js

js 复制代码
const { defineConfig } = require('@vue/cli-service');
const { name } = require('./package.json');

module.exports = defineConfig({
    transpileDependencies: true,
    devServer: {
        port: 3001,
        headers: {
          'Access-Control-Allow-Origin': '*',
        },
    },
    configureWebpack: {
        output: {
          library: `${name}-[name]`,
          libraryTarget: 'umd',
          // webpack 5 需要把 jsonpFunction 替换成 chunkLoadingGlobal
          chunkLoadingGlobal: `webpackJsonp_${name}`, 
        },
    },
})

上述,修改项目配置文件本质就做三件事情:

  • 端口:子应用的端口与主应用注册的端口保持一致。
  • 跨域:子应用的代理服务允许跨域,否则,当主应用加载子应用的静态资源时会产生跨域的情况。
  • 命名空间:给每个子应用的代码块加载逻辑创建了一个独立的命名空间。

在 Webpack 5 中,为了更好地管理代码块(chunk)的加载,尤其是在微前端环境下避免多个子应用之间代码块加载的冲突,引入了 chunkLoadingGlobal 配置来替换之前的 jsonpFunction

启动项目:

js 复制代码
npm run serve

新建 src/public-path.js 文件:

js 复制代码
if (window.__POWERED_BY_QIANKUN__) {
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

然后,再来修改 main.js 文件,qiankun 要求子应用在其入口文件中正确导出特定的生命周期函数,其实就是要导出这三个函数:bootstrap/mount/unmount

js 复制代码
import './public-path.js';
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

let instance = null;

function render(props = {}) {
    console.log('子应用(yd-vue2)', props);
    const { container } = props;
    instance = new Vue({
        render: h => h(App),
    }).$mount(container ? container.querySelector('#app') : '#app')
}

// 非qiankun环境下,也能独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
    render();
}

/**
 * bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
 * 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
 */
export async function bootstrap() {}
/**
 * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
 */
export async function mount(props) {
    render(props);
}
/**
 * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
 */
export async function unmount() {
    instance.$destroy();
    instance.$el.innerHTML = '';
    instance = null;
}

三个生命周期函数的作用可以看看小编写的注释,也可以瞧瞧文档的描述。而其他代码的调整主要是为了让这个项目在非 qiankun 的环境下也能独立运行。

还有,最开头引入 ./public-path.js 文件,进行路径的重写,具体解释如下:

  • window.__POWERED_BY_QIANKUN__是 qiankun 框架在子应用运行环境中设置的一个标识变量。这个变量用于判断子应用是否是在 qiankun 微前端环境下运行。

  • __webpack_public_path__是 Webpack 用于指定公共资源加载路径的变量。当子应用在 qiankun 环境下运行时,可以通过设置:

    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__

    子应用的资源加载路径会被动态地修改为 qiankun 框架指定的路径(window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__是 qiankun 注入的公共路径变量)。这样可以确保子应用的资源(如 JavaScript 文件、CSS 文件等)能够正确地被主应用加载,避免因为资源加载路径错误而导致子应用无法正常运行。

修改后,子应用就算接入到主应用中了,如何来验证呢❓

你可以在主应用中去访问子应用注册的路由入口:

React18

初始化项目:

js 复制代码
create-react-app my-app --template typescript

(Vue 的小伙伴可能需要全局先装一下 React 的脚手架,npm install -g create-react-app

安装 react-app-rewired 依赖,它的作用是对 Webpack 的配置进行重写:

js 复制代码
npm install react-app-rewired -D

重写 Webpack 的配置也可以使用 @rescripts/cli 依赖。

但是,由于小编本次项目使用的是 React18 版本,它的 react-scripts 依赖版本到了 5.x.x,而 @rescripts/cli 要求其版本只能是 "2~4" (react-scripts@"2 - 4" from @rescripts/cli@0.0.16),所以,小编才采用了 react-app-rewired 依赖。

(其实你也可以使用 --force 强制忽略进行安装,但是还是不推荐。💀 npm install @rescripts/cli --force

它们俩的配置文件命名不同:

@rescripts/cli.rescriptsrc.js

react-app-rewiredconfig-overrides.js

但它们两者都用于在基于 create-react-app 构建的项目中自定义 Webpack 配置,只是实现方式和侧重点略有不同。

根目录下新建 config-overrides.js 文件:

js 复制代码
const { name } = require('./package.json');

module.exports = {
  webpack: (config) => {
    config.output.library = `${name}-[name]`;
    config.output.libraryTarget = 'umd';
    // webpack 5 需要把 jsonpFunction 替换成 chunkLoadingGlobal
    config.output.chunkLoadingGlobal = `webpackJsonp_${name}`;
    config.output.globalObject = 'window';

    return config;
  },

  devServer: (_) => {
    const config = _;
    config.headers = {
      'Access-Control-Allow-Origin': '*',
    };
    config.historyApiFallback = true;
    config.hot = true;
    config.watchContentBase = false;
    config.liveReload = false;

    return config;
  },
};

修改 package.json 文件,先仅修改 start 命令玩玩就行:

js 复制代码
{
    "scripts": {
       // "start": "react-scripts start",
       "start": "react-app-rewired start",
    }
}

修改项目端口号,根目录下新建 .env 文件:

js 复制代码
PORT=3002

(使用 create-react-app构建的 React 项目中,支持直接通过 .env 文件来配置项目环境变量,如端口号等等)

启动项目:

js 复制代码
npm start

新建 src/public-path.js 文件:

js 复制代码
if (window.__POWERED_BY_QIANKUN__) {
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

修改项目入口文件(index.tsx),一样导出三个生命周期函数:

js 复制代码
import './public-path.js'
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

let root: ReactDOM.Root | null = null;

function render(props: any) {
    console.log('子应用(yd-react)', props);
    const { container } = props;
    root = ReactDOM.createRoot(
        container ? container.querySelector('#root') : document.querySelector('#root') as HTMLElement
    );
    root.render(
        <React.StrictMode>
            <App />
        </React.StrictMode>
    );
}

// @ts-ignore
if (!window.__POWERED_BY_QIANKUN__) {
    render({});
}

export async function bootstrap() {}
export async function mount(props: any) {
    render(props);
}
export async function unmount() {
    root!.unmount();
}

reportWebVitals();

这就没啥可说的了,一样的操作,导出生命周期函数,并且将其改造成非 qiankun 下也能独立运行的形式。

如果你出现了主应用查看子应用时,静态资源(图片、字体)有报错或者跨域行为,如:

你可以检查配置文件是否正确配置了跨域,或者是子应用的入口文件最开头是否引入了 public-path.js 文件。

正确的静态资源文件引入时,应当是带有子应用的前缀,如下图。👇👇👇

最后,记得去主应用中访问子应用注册的路由入口进行验证。

Vue3

初始化项目:

js 复制代码
npm create vite@latest

安装 vite-plugin-qiankun 依赖,它能帮助咱们快速接入 qiankun 环境:

js 复制代码
npm install vite-plugin-qiankun -D

修改项目配置文件(vite.config.ts):

js 复制代码
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import qiankun from "vite-plugin-qiankun";
import { name } from './package.json';

export default defineConfig({
    plugins: [
        vue(),
        // 接入qiankun环境
        qiankun(name, {
            useDevMode: true,
        }),
    ],
    server: {
        port: 3003,
        headers: {
            "Access-Control-Allow-Origin": "*",
        },
    },
});

启动项目:

js 复制代码
npm run dev

修改入口文件(main.ts):

js 复制代码
import { createApp } from 'vue'
import App from './App.vue'
import { renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper';

let app: any = undefined;

const render = (props: any) => {
    console.log('子应用(yd-vue3)', props);
    const { container } = props;
    app = createApp(App)
    app.mount(container ? container.querySelector('#app') : '#app')
}

const initQianKun = () => {
    renderWithQiankun({
        bootstrap() {},
        mount(props) {
            render(props);
        },
        unmount() {
            app.unmount()
        },
        update() {}
    })
}

qiankunWindow.__POWERED_BY_QIANKUN__ ? initQianKun() : render({});

Vue3 在插件的配合下,接入 qiankun 环境简直就是手到擒来啦~👻

应用之间的通信

微前端的架构很好的解决了传统单体前端应用面临的各种问题,不过,由于将整个应用拆分成多个小型、相对独立的子应用。然而,拆分后的各个子应用并非孤立存在,它们之间往往需要进行数据交互与通信,以保证业务流程的连贯性与整体性。

但是呢,在绝大多数场景下,咱们着重考量的往往是主应用与子应用间的通信事宜。要知道,子应用通常是以路由作为关键区分基准的,基于此特性,子应用彼此间直接通信的情况较为少见。即便确实存在这类需求,大概率也会借助主应用来搭建一座临时 "沟通桥梁",以此实现信息交互。

既然如此,接下来摆在我们面前、亟待深入探讨的关键问题便是:主应用与子应用究竟该如何高效、精准地进行通信呢❓

qiankun 的架构中提供了 Action 的通信方式,但它比较适合业务划分清晰,项目情况比较简单的微前端应用。在复杂一点的业务系统中可能就不是很好用了,而且好像听说官方要弃用了❓反正这次咱们就不使用了,也不讲解,感兴趣的小伙伴可以自行上官网了解哈。😋

这次咱们通过 "发布/订阅" 的设计模式来自行编写一个通讯模块,它基于 window 对象。🌐

在主应用中,新建 qiankun.js 文件,直接贴上代码瞧瞧:

js 复制代码
if (window.__POWERED_BY_QIANKUN__) {
    // 仅在子应用下执行
    window['__webpack_public_path__'] = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

window._QIANKUN_YD = window._QIANKUN_YD || {
    // 通信
    event: (() => {
        class Emitter {
            constructor() {
                this.events = {};
                this.watchs = [];
            }
            add(eventName, callback, count) {
                if (!eventName || typeof callback !== "function") return;
                if (!this.events[eventName]) {
                    this.events[eventName] = [];
                    this.events[eventName].push({ callback, count });
                } else {
                    const hasExist = this.events[eventName].some(
                        (item) => item.callback === callback && item.count === count
                    );
                    !hasExist && this.events[eventName].push({ callback, count });
                }
            }
            emit(...args) {
                const [eventName, ...restArgs] = args;
                const callbacks = this.events[eventName] || [];
                if (eventName && this.watchs.length > 0) {
                    this.watchs.forEach((item) => {
                        item.apply(this, [eventName, ...restArgs]);
                    });
                }
                if (eventName && callbacks.length > 0) {
                    callbacks.forEach(({ callback, count }) => {
                        callback.apply(this, [eventName, ...restArgs]);
                        count && this.off(eventName, callback);
                    });
                }
            }
            on(eventName, callback) {
                this.add(eventName, callback, 0);
            }
            once(eventName, callback) {
                this.add(eventName, callback, 1);
            }
            off(eventName, callback) {
                const callbacks = this.events[eventName] || [];
                if (callbacks.legnth <= 0) return;
                if (!callback) this.events[eventName] = [];
                callbacks.forEach((item, index) => {
                    if (item.callback === callback) {
                        callbacks.splice(index, 1);
                    }
                });
            }
            watch(callback) {
                if (typeof fn !== 'function') return;
                this.watchs.push(callback);
            }
        }
        return new Emitter();
    })(),
    // 数据共享(具备持久化能力)
    store: (() => {})(),
};

大概五十行代码,虽然没有写注释,但是,俺相信你应该能看得懂哈😁,很标准的一个发布/订阅的设计模式。

然后,在主应用的入口文件( main.ts)中引入:

js 复制代码
import './qiankun.js';
import { createApp } from 'vue'

// ...

咱们再来假设一个场景,如让子应用主动与父应用通信 ,具体场景可以是子应用去打开主应用的全局 loading 交互。

咱们先来主应用中订阅 loading 事件,主应用 App.vue 文件:

js 复制代码
<script lang="ts" setup>
import { onMounted } from 'vue';
import { ElLoading } from 'element-plus';

onMounted(() => {
    // 订阅loading事件
    window._QIANKUN_YD.event.on('loading', () => {
        const loadingInstance = ElLoading.service({ fullscreen: true });
        setTimeout(() => {
            loadingInstance.close()
        }, 3000);
    })
})
</script>

然后,子应用中(以 yd-vue2 为例),同样新建 qiankun.js 文件,文件内容是一样的。

在子应用的入口文件( main.js)中引入:

js 复制代码
import './qiankun.js';
// import './public-path.js'; // public-path.js文件的内容也整合在qiankun.js文件中了
import Vue from 'vue'

// ...

子应用 App.vue 文件发布 loading 事件去触发主应用的订阅:

js 复制代码
<template>
  <div id="app">
    <button @click="openLoading">打开全局的loading</button>
  </div>
</template>

<script>
export default {
    methods: {
        openLoading() {
            // 发布loading事件
            window._QIANKUN_YD.event.emit('loading')
        }
    }
}
</script>

效果:

这样子就完成了子应用主动与主应用之间的通信了,同样,如果是主应用要主动与子应用通信,反过来操作一遍就行啦。😝

发布订阅模式在微前端 qiankun 架构下,能成功解耦各应用间直接依赖,以松散灵活结构实现多元技术栈应用高效通信,契合复杂业务系统持续迭代、扩展需求,助力前端项目高效协同、稳健运行。无论是简单数据传递,还是复杂业务联动,都能依托该通信方案达成无缝交互,发挥微前端架构最大效能。当然,这些都是后话了,反正,灵活、方便、不受限技术栈就是咱们的最终追求。😁

_QIANKUN_YD.event 相关 API 说明:

语法 说明
window._QIANKUN_YD.event.on(eventName, cb) 订阅
window._QIANKUN_YD.event.one(eventName, cb) 订阅,仅执行一次
window._QIANKUN_YD.event.emit(eventName, arg1, arg2, ...) 发布
window._QIANKUN_YD.event.off(eventName, cb) 解除订阅
window._QIANKUN_YD.event.watch(eventName, cb) 监听所有的订阅事件

数据共享(持久化)

不过,你不会以为这就完了吧?🙊

现在咱们再来考虑一个场景,例如:用户在主应用中进行登录,紧接着跳转去访问子应用。按常理来讲,此时子应用可不该让用户再进行登录了,而应当无缝衔接,直接沿用主应用的登录状态才对。说白了,这背后的核心操作就是要把主应用登录成功后获取到的 "身份令牌"------ 也就是 Token,巧妙地传递给子应用,好让子应用能揣着它大摇大摆地去访问接口,畅通无阻地获取所需数据。

这个场景在微前端架构中属于非常常见的情况了,但是,面对这个问题场景,咱们来琢磨琢磨前面讲过的发布/订阅的设计模式通信能满足这种场景吗❓

好像不太行是吧?🙈🙈🙈

这个时候就需要来考虑新的形式了,有没有注意到小编在 qiankun.js 文件中还留一个后手❗store 就是用来解决这类场景的,并且它也具备持久化的能力。

直接贴代码来瞧瞧:

js 复制代码
// ...

window._QIANKUN_YD = window._QIANKUN_YD || {
    event: (() => { 
        // ...
    })(),
    store: (() => {
        class Storage {
            constructor() {
                // 持久化
                this.storage = generatorStorage(window.localStorage);
                // this.storage = generatorStorage(window.sessionStorage);
                // 全局数据,生命周期同window
                this.global = {};
            }
            set(key, value) {
                return this.storage.set(key, value);
            }
            get(key) {
                return this.storage.get(key);
            }
            remove(key) {
                this.storage.remove(key);
            }
            clear() {
                this.storage.clear();
            }
            setGlobalData(key, value) {
                this.global[key] = value;
            }
            getGlobalData(key) {
                return this.global[key];
            }
        }
        return new Storage();
    })(),
};

function generatorStorage(storage) {
    const prefix = (key) => `__qiankun_yd_${key}`;
    return {
        set(key, value) {
            const valueFormat = (value) => {
                if (
                    ["[object Array]", "[object Object]"].includes(
                        Object.prototype.toString.call(value)
                    )
                ) {
                    return JSON.stringify(value);
                } else {
                    return value;
                }
            };
            storage.setItem(prefix(key), valueFormat(value));
        },
        get(key) {
            try {
                return JSON.parse(storage.getItem(prefix(key)));
            } catch {
                return storage.getItem(prefix(key));
            }
        },
        remove(key) {
            storage.removeItem(prefix(key));
        },
        clear() {
            storage.clear();
        },
    };
}

在主应用中模拟登陆情况,App.vue 文件:

js 复制代码
<template>
    <el-menu
    mode="horizontal"
    >
        <el-menu-item index="1"><router-link to="/yd-vue2">子应用(vue2)</router-link></el-menu-item>
        <el-menu-item index="2"><router-link to="/yd-react">子应用(react)</router-link></el-menu-item>
        <el-menu-item index="3"><router-link to="/yd-vue3">子应用(vue3)</router-link></el-menu-item>
    </el-menu>
    <h1>主应用内容:</h1>
    <button @click="login">登录</button>
    <h1>以下为子应用内容:</h1>
    <div id="yd-container"></div>
</template>

<script lang="ts" setup>
import { onMounted } from 'vue'
import { ElLoading } from 'element-plus'
import { useRouter  } from 'vue-router'

const router = useRouter()
function login() {
    const loadingInstance = ElLoading.service({ fullscreen: true });
    setTimeout(() => {
        loadingInstance.close();
        // 登录成功,储存token
        window._QIANKUN_YD.store.set('token', '我是一个token');
        // 跳转去访问子应用
        router.push('/yd-vue2');
    }, 1000);
}
</script>

在子应用的入口文件中获取 Token 用于后续的请求,main.js 文件:

js 复制代码
import './qiankun';
import Vue from 'vue'
import App from './App.vue'

// 获取主应用的token
const token = window._QIANKUN_YD.store.get('token');
console.log('[yd-vue2] 这是从主应用中获取的token:', token);

// ...

在子应用入口文件中能正确获取到主应用的 Token 基本就算大功告成🥳。当然,在日常的开发里,多数情形下咱们并不会在 main.js 文件里使用到 Token,你可以在你自个封装的 axiosfetch 或是其他类似的请求处理模块的拦截器中,同样去获取主应用的 Token,并将其携带至实际的请求中去。

这应该不难理解哈,原理挺简单的,我们借助了 window.localStorage API 来实现数据共享,也侧面到达一个主应用与子应用通信的效果。并且 window.localStorage API 具备持久化能力,在项目实践中,如用户偏好设置、历史记录等需要长期稳固存储数据的场景,咱们也可以使用这个 _QIANKUN_YD.store 来完成。

_QIANKUN_YD.store 相关 API 说明:

语法 说明
window._QIANKUN_YD.store.set(key, value) 储存数据,具备持久化
window._QIANKUN_YD.store.get(key) 获取数据
window._QIANKUN_YD.store.remove(key) 清除单项数据
window._QIANKUN_YD.store.clear() 清除全部数据
window._QIANKUN_YD.store.setGlobalData(key, value) 储存全局数据,生命周期同window
window._QIANKUN_YD.store.getGlobalData(key, value) 获取全局数据

至此,本篇文章就写完啦,撒花撒花。

相关推荐
ZZZCY20039 分钟前
路由策略与路由控制实验
前端·网络
shawya_void18 分钟前
javaweb-day01-html和css初识
前端·css·html
khatung19 分钟前
React——useReducer
前端·javascript·vscode·react.js·前端框架·1024程序员节
思考的橙子21 分钟前
CSS之3D转换
前端·css·3d
运维&陈同学43 分钟前
【kafka01】消息队列与微服务之Kafka详解
运维·分布式·后端·微服务·云原生·容器·架构·kafka
木子七1 小时前
vue3-setup中使用响应式
前端·vue
廖子默1 小时前
提供html2canvas+jsPDF将HTML页面以A4纸方式导出为PDF后,内容分页时存在截断的解决思路
前端·pdf·html
Moment1 小时前
毕业半年,终于拥有了两个近 500 star 的开源项目了 🤭🤭🤭
前端·后端·开源
光影少年1 小时前
react和vue图片懒加载及实现原理
前端·vue.js·react.js
AndyGoWei1 小时前
react react-router-dom history 实现原理,看这篇就够了
前端·javascript·react.js