微前端(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/[email protected]),所以,小编才采用了 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) 获取全局数据

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

相关推荐
庸俗今天不摸鱼13 分钟前
【万字总结】前端全方位性能优化指南(十)——自适应优化系统、遗传算法调参、Service Worker智能降级方案
前端·性能优化·webassembly
黄毛火烧雪下20 分钟前
React Context API 用于在组件树中共享全局状态
前端·javascript·react.js
Apifox31 分钟前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员
一张假钞33 分钟前
Firefox默认在新标签页打开收藏栏链接
前端·firefox
高达可以过山车不行34 分钟前
Firefox账号同步书签不一致(火狐浏览器书签同步不一致)
前端·firefox
m0_5937581035 分钟前
firefox 136.0.4版本离线安装MarkDown插件
前端·firefox
掘金一周38 分钟前
金石焕新程 >> 瓜分万元现金大奖征文活动即将回归 | 掘金一周 4.3
前端·人工智能·后端
三翼鸟数字化技术团队1 小时前
Vue自定义指令最佳实践教程
前端·vue.js
Jasmin Tin Wei1 小时前
蓝桥杯 web 学海无涯(axios、ecahrts)版本二
前端·蓝桥杯
圈圈编码2 小时前
Spring Task 定时任务
java·前端·spring