介紹 qiankun
在正式介紹 qiankun 之前,我們需要知道,qiankun 是一個基于 single-spa 的微前端實作庫,旨在幫助大家能更簡單、無痛的建構一個生産可用微前端架構系統。
微前端的概念借鑒自後端的微服務,主要是為了解決大型工程在變更、維護、擴充等方面的困難而提出的。目前主流的微前端方案包括以下幾個:
- iframe
- 基座模式,主要基于路由分發,qiankun 和 single-spa 就是基于這種模式
- 組合式內建,即單獨建構元件,按需加載,類似 npm 包的形式
- EMP,主要基于 Webpack5 Module Federation
- Web Components
嚴格來講,這些方案都不算是完整的微前端解決方案,它們隻是用于解決微前端中運作時容器的相關問題。
本文我們主要對 qiankun 所基于的基座模式進行介紹。它的主要思路是将一個大型應用拆分成若幹個更小、更簡單,可以獨立開發、測試和部署的微應用,然後由一個基座應用根據路由進行應用切換。
qiankun 的核心設計理念
-
🥄 簡單
由于主應用微應用都能做到技術棧無關,qiankun 對于使用者而言隻是一個類似 jQuery 的庫,你需要調用幾個 qiankun 的 API 即可完成應用的微前端改造。同時由于 qiankun 的 HTML entry 及沙箱的設計,使得微應用的接入像使用 iframe 一樣簡單。
-
🍡 解耦/技術棧無關
微前端的核心目标是将巨石應用拆解成若幹可以自治的松耦合微應用,而 qiankun 的諸多設計均是秉持這一原則,如 HTML entry、沙箱、應用間通信等。這樣才能確定微應用真正具備 獨立開發、獨立運作 的能力。
特性
- 📦 基于 single-spa 封裝,提供了更加開箱即用的 API。
- 📱 技術棧無關,任意技術棧的應用均可 使用/接入,不論是 React/Vue/Angular/JQuery 還是其他等架構。
- 💪 HTML Entry 接入方式,讓你接入微應用像使用 iframe 一樣簡單。
- 🛡 樣式隔離,確定微應用之間樣式互相不幹擾。
- 🧳 JS 沙箱,確定微應用之間 全局變量/事件 不沖突。
- ⚡️ 資源預加載,在浏覽器空閑時間預加載未打開的微應用資源,加速微應用打開速度。
- 🔌 umi 插件,提供了 @umijs/plugin-qiankun 供 umi 應用一鍵切換成微前端架構系統。
項目實戰
本文适合剛接觸的新人,介紹了如何從 0 建構一個
qiankun
項目。項目主要有以下構成:
qiankun
- 主應用:
- 使用 umi3.5,未使用 @umijs/plugin-qiankun,而是直接使用的 qiankun
- vue 微應用:
- 使用 vue2.x 建立
- 使用 vue3.x,暫未使用 vite 建構,目測 vite 不相容
- react 微應用:
- 使用 create-react-app 建立
- umi3 微應用:
- 使用 umi3.結合插件 @umijs/plugin-qiankun
- 非 webpack 建構的微應用:
- 一些非 webpack 建構的項目,例如 jQuery 項目、jsp 項目,都可以按照這個處理。
- 接入之前請確定你的項目裡的圖檔、音視訊等資源能正常加載,如果這些資源的位址都是完整路徑(例如 https://qiankun.umijs.org/logo.png ),則沒問題。如果都是相對路徑,需要先将這些資源上傳到伺服器,使用完整路徑。
- Angular 微應用:
- 使用的 @angular/[email protected] 版本
主應用環境搭建
主應用按照官方的說法,不限技術棧,隻需要提供一個容器 DOM,然後注冊微應用并 start 即可。這裡我們使用 umi 來初始化。
初始化 & 安裝 qiankun
# 項目初始化
$ yarn create @umijs/umi-app
# 安裝依賴
$ yarn
# 啟動
$ yarn start
# 安裝 qiankun
$ yarn add qiankun
基本環境搭建完成,在主應用中增加一些菜單和路由,用于主應用頁面以及主應用和微應用之間切換操作。頁面布局和路由配置這裡不做過多介紹,文末會奉上源碼。大緻頁面如下圖:
主應用中注冊微應用
注冊微應用的基礎配置資訊。當浏覽器 url 發生變化時,會自動檢查每一個微應用注冊的 activeRule 規則,符合規則的應用将會被自動激活。本示列分别有一個主應用五個微應用構成,在主應用中增加微應用的配置檔案,對注冊微應用做單獨的管理。
注冊微應用基本配置
主應用 src 檔案下增加 registerMicroAppsConfig.ts
,内容如下:
const loader = (loading: boolean) => {
// 此處可以擷取微應用是否加載成功,可以用來觸發全局的 loading
console.log("loading", loading);
};
export const Microconfig = [
//name: 微應用的名稱,
//entry: 微應用的入口,
//container: 微應用的容器節點的選擇器或者 Element 執行個體,
//activeRule: 激活微應用的規則(可以比對到微應用的路由),
//loader: 加載微應用的狀态 true | false
{
name: "vue2",
entry: "http://localhost:8001",
container: "#subContainer",
activeRule: "/vue2",
loader,
},
{
name: "vue3",
entry: "http://localhost:8002",
container: "#subContainer",
activeRule: "/vue3",
loader,
},
{
name: "react",
entry: "http://localhost:8003",
container: "#subContainer",
activeRule: "/react",
loader,
},
{
name: "umi",
entry: "http://localhost:8004",
container: "#subContainer",
activeRule: "/umi",
loader,
},
{
name: "purehtml",
entry: "http://127.0.0.1:8005",
container: "#subContainer",
activeRule: "/purehtml",
loader,
},
//angular
{
name: "angular",
entry: "http://127.0.0.1:8006",
container: "#subContainer",
activeRule: "/angular",
loader,
},
];
主應用入口檔案引入(主應用使用的 umi,是以直接在 pages/index.tsx 引入)
import LayoutPage from "@/layout/index";
import {
registerMicroApps,
start,
addGlobalUncaughtErrorHandler,
} from "qiankun";
import { Microconfig } from "@/registerMicroAppsConfig";
// 注冊微應用
registerMicroApps(Microconfig, {
// qiankun 生命周期鈎子 - 微應用加載前
beforeLoad: (app: any) => {
console.log("before load", app.name);
return Promise.resolve();
},
// qiankun 生命周期鈎子 - 微應用挂載後
afterMount: (app: any) => {
console.log("after mount", app.name);
return Promise.resolve();
},
});
// 啟動 qiankun
start();
export default function IndexPage({ children }: any) {
return (
<LayoutPage>
<div>{children}</div>
{/* 增加容器,用于顯示微應用 */}
<div id="subContainer"></div>
</LayoutPage>
);
}
添加全局異常捕獲
// 添加全局異常捕獲
addGlobalUncaughtErrorHandler((handler) => {
console.log("異常捕獲", handler);
});
開啟預加載&沙箱模式
- ⚡️prefetch: 開啟預加載
- true | ‘all’ | string[] | function
- 🧳sandbox:是否開啟沙箱
- strictStyleIsolation 嚴格模式(
)ShadowDOM
- experimentalStyleIsolation 實驗性方案,建議使用
- strictStyleIsolation 嚴格模式(
start({
prefetch: true, // 開啟預加載
sandbox: {
experimentalStyleIsolation: true, // 開啟沙箱模式,實驗性方案
},
});
設定主應用啟動後預設進入的微應用
import { setDefaultMountApp } from "qiankun"
setDefaultMountApp('/purehtml');
建立對應的微應用
注意微應用的名稱=>
package.json
需要和主應用中注冊時的
name
相對應,且必須確定唯一。
name
微應用 vue2.x
初始化
# 安裝 vueCli
$ yarn add @vue/cli
# 建立項目
$ vue create vue2.x_root
# 選擇 vue2 版本
# 安裝依賴
$ yarn
# 啟動
$ yarn serve
改造成微應用
- 在
目錄新增src
:public-path.js
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
- 入口檔案
修改main.js
import "./public-path";
import Vue from "vue";
import App from "./App.vue";
import VueRouter from "vue-router";
import routes from "./router";
Vue.config.productionTip = false;
let router = null;
let instance = null;
function render(props = {}) {
const { container } = props;
router = new VueRouter({
// 注意這裡的name,最好不要寫死,直接使用主應用傳過來的name
base: window.__POWERED_BY_QIANKUN__ ? `${props.name}` : "/",
mode: "history",
routes,
});
Vue.use(VueRouter);
instance = new Vue({
router,
render: (h) => h(App),
}).$mount(container ? container.querySelector("#app") : "#app");
}
// 獨立運作時
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
export async function bootstrap() {
console.log("[vue2] vue app bootstraped");
}
export async function mount(props) {
render(props);
}
export async function unmount() {
instance.$destroy();
instance.$el.innerHTML = "";
instance = null;
router = null;
}
- 打包配置修改(
):vue.config.js
const path = require("path");
const { name } = require("./package");
function resolve(dir) {
return path.join(__dirname, dir);
}
module.exports = {
filenameHashing: true,
lintOnSave: process.env.NODE * ENV !== "production",
runtimeCompiler: true,
productionSourceMap: false,
devServer: {
hot: true,
disableHostCheck: true,
// 修改預設端口,和注冊時一直
port: 8001,
overlay: {
warnings: false,
errors: true,
},
// 解決主應用加載子應用出現跨域問題
headers: {
"Access-Control-Allow-Origin": "*",
},
},
// 自定義 webpack 配置
configureWebpack: {
resolve: {
alias: {
"@": resolve("src"),
},
},
// 讓主應用能正确識别微應用暴露出來的一些資訊
output: {
library: `${name}-[name]`,
libraryTarget: "umd", // 把子應用打包成 umd 庫格式
jsonpFunction: `webpackJsonp*${name}`,
},
},
};
- 主應用檢視加載效果
微應用 vue3.x
初始化
# 安裝 vueCli
$ yarn add @vue/cli
# 建立項目
$ vue create vue3.x_root
# 選擇 vue3 版本
# 安裝依賴
$ yarn
# 啟動
$ yarn serve
改造成微應用
- 在
目錄新增src
:public-path.js
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
- 入口檔案
修改main.ts
//@ts-nocheck
import './public-path';
import { createApp } from 'vue';
import { createRouter, createWebHistory } from 'vue-router';
import App from './App.vue';
import routes from './router';
import store from './store';
let router = null;
let instance = null;
let history = null;
function render(props = {}) {
const { container } = props;
history = createWebHistory(window.__POWERED_BY_QIANKUN__ ? `${props.name}` : '/');
router = createRouter({
history,
routes,
});
instance = createApp(App);
instance.use(router);
instance.use(store);
instance.mount(container ? container.querySelector('#app') : '#app');
}
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
export async function bootstrap() {
console.log('%c ', 'color: green;', 'vue3.0 app bootstraped');
}
export async function mount(props) {
render(props);
}
export async function unmount() {
instance.unmount();
instance._container.innerHTML = '';
instance = null;
router = null;
history.destroy();
}
- 打包配置修改(
):vue.config.js
const path = require('path')
const { name } = require('./package')
function resolve (dir) {
return path.join(__dirname, dir)
}
module.exports = {
filenameHashing: true,
lintOnSave: process.env.NODE_ENV !== 'production',
runtimeCompiler: true,
productionSourceMap: false,
devServer: {
hot: true,
disableHostCheck: true,
// 修改預設端口,和注冊時一直
port: 8002,
overlay: {
warnings: false,
errors: true
},
headers: {
'Access-Control-Allow-Origin': '*'
}
},
// 自定義webpack配置
configureWebpack: {
resolve: {
alias: {
'@': resolve('src')
}
},
// 讓主應用能正确識别微應用暴露出來的一些資訊
output: {
library: `${name}-[name]`,
libraryTarget: 'umd', // 把子應用打包成 umd 庫格式
jsonpFunction: `webpackJsonp_${name}`
}
}
}
- 主應用檢視加載效果
微應用 react
初始化
# 建立項目
$ yarn add create-react-app react_root
# 啟動
$ yarn start
改造成微應用
- 在
目錄新增src
:public-path.js
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
- 設定 history 模式路由的 base:
剛剛建立的項目沒有路由,是以先要安裝路由
# 路由安裝
$ yarn add react-router react-router-dom
入口檔案 index.js 修改,為了避免根 id #root 與其他的 DOM 沖突,需要限制查找範圍。
import './public-path';
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { BrowserRouter, Route, Link } from "react-router-dom"
function render(props) {
const { container } = props;
ReactDOM.render(
<BrowserRouter basename={window.__POWERED_BY_QIANKUN__ ? '/react' : '/'}>
<App/>
</BrowserRouter>
, container ? container.querySelector('#root') : document.querySelector('#root'));
}
if (!window.__POWERED_BY_QIANKUN__) {
render({});
}
export async function bootstrap() {
console.log('[react16] react app bootstraped');
}
export async function mount(props) {
console.log('[react16] props from main framework', props);
render(props);
}
export async function unmount(props) {
const { container } = props;
ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root'));
}
- webpack 打包配置修改
安裝插件 @rescripts/cli,當然也可以選擇其他的插件,例如 react-app-rewired
# 安裝
$ yarn add @rescripts/cli
根目錄增加配置檔案 .rescriptsrc.js
,注意一定是根目錄下哦
const { name } = require('./package');
module.exports = {
webpack: (config) => {
config.output.library = `${name}-[name]`;
config.output.libraryTarget = 'umd';
config.output.jsonpFunction = `webpackJsonp_${name}`;
config.output.globalObject = 'window';
return config;
},
devServer: (_) => {
const config = _;
config.headers = {
'Access-Control-Allow-Origin': '*',
};
config.historyApiFallback = true;
config.hot = false;
config.watchContentBase = false;
config.liveReload = false;
return config;
},
};
-
配置修改package.json
{
"name": "react_root",
"version": "0.1.0",
"private": true,
"dependencies": {
"@rescripts/cli": "^0.0.16",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router-dom": "5.0",
"react-scripts": "4.0.3",
"web-vitals": "^1.0.1"
},
"scripts": {
"start": "set PORT=8003&&rescripts start",
"build": "rescripts build",
"test": "rescripts test",
"eject": "rescripts eject"
},
"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"
]
}
}
- 主應用檢視加載效果
微應用 umi
umi 項目初始化方式參考初始化主應用的方式。umi 應用使用 @umijs/plugin-qiankun
可以一鍵開啟微前端模式。
啟用方式
- 安裝插件
# 安裝 @umijs/plugin-qiankun
$ yarn add @umijs/plugin-qiankun
- 修改配置檔案
umirc.ts
如果是配置檔案抽離到
中,直接修改config
config.js
import { defineConfig } from 'umi';
export default defineConfig({
nodeModulesTransform: {
type: 'none',
},
routes: [
{ path: '/', component: '@/pages/index' },
],
fastRefresh: {},
//開啟qiankun配置
qiankun:{
slave:{
}
}
});
這裡隻是做了簡單的內建配置,更過功能請參看@umijs/plugin-qiankun
- 加載效果
微應用非 webpack 應用
非 webpack 應用有個需要注意點的點:接入之前請確定你的項目裡的圖檔、音視訊等資源能正常加載,如果這些資源的位址都是完整路徑(例如 https://qiankun.umijs.org/logo.png),則沒問題。如果都是相對路徑,需要先将這些資源上傳到伺服器,使用完整路徑。
- 入口檔案聲明
入口entry
<!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>Document</title>
</head>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<body>
<div id="test">測試微應用</div>
</body>
</html>
<!-- entry 入口 -->
<script src="./index.js" entry></script>
- index.js
const render = ($) => {
// 這裡可以在渲染之前做些什麼。。。
return Promise.resolve();
};
((global) => {
//purehtml 是對應的微應用名稱
global['purehtml'] = {
bootstrap: () => {
console.log('purehtml bootstrap');
return Promise.resolve();
},
mount: (props) => {
console.log('purehtml mount00000000000',props);
props.onGlobalStateChange((state,prev)=>{
console.log(state,prev)
})
return render($);
},
unmount: () => {
console.log('purehtml unmount');
return Promise.resolve();
},
};
})(window);
- 為了友善啟動和加載,使用
啟動本地服務http-server
根目錄增加
檔案, 注意package.json
:name
purehtml
{
"name": "purehtml",
"version": "1.0.0",
"description": "",
"main": "index.html",
"scripts": {
"start": "cross-env PORT=8005 http-server . --cors",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "MIT",
"devDependencies": {
"cross-env": "^7.0.2",
"http-server": "^0.12.1"
}
}
- 加載效果
微應用 Angular
初始化
# 安裝 CLI
$ yarn add -g @angular/[email protected]
# 建立項目
$ ng new angular_root
# 啟動
$ ng serve
改造成微應用
- 在
目錄新增src
:public-path.js
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
}
- 設定 history 模式路由的 base,
檔案:src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { APP_BASE_HREF } from '@angular/common';
const routes: Routes = [];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
// @ts-ignore
providers: [{ provide: APP_BASE_HREF, useValue: window.__POWERED_BY_QIANKUN__ ? '/angular' : '/' }]
})
export class AppRoutingModule { }
- 修改入口檔案,src/main.ts 檔案
import './public-path';
import { enableProdMode, NgModuleRef } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
let app: void | NgModuleRef<AppModule>;
async function render() {
app = await platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch((err) => console.error(err));
}
if (!(window as any).__POWERED_BY_QIANKUN__) {
render();
}
export async function bootstrap(props: Object) {
console.log(props);
}
export async function mount(props: Object) {
render();
}
export async function unmount(props: Object) {
console.log(props);
// @ts-ignore
app.destroy();
}
- 修改 webpack 打包配置
根據官方訓示:先安裝
,注意:angular 9 項目隻能安裝 9.x 版本,angular 10 項目可以安裝最新版。@angular-builders/custom-webpack
$ yarn add @angular-builders/[email protected]
在根目錄增加 custom-webpack.config.js
const appName = require('./package.json').name;
module.exports = {
devServer: {
headers: {
'Access-Control-Allow-Origin': '*',
},
},
output: {
library: `${appName}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${appName}`,
},
};
修改 angular.json 配置檔案
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"angularRoot": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-builders/custom-webpack:browser",
"options": {
"outputPath": "dist/angularRoot",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"aot": true,
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": [],
"customWebpackConfig": {
"path": "./custom-webpack.config.js"
}
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "10kb"
}
]
}
}
},
"serve": {
"builder": "@angular-builders/custom-webpack:dev-server",
"options": {
"browserTarget": "angularRoot:build"
},
"configurations": {
"production": {
"browserTarget": "angularRoot:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "angularRoot:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": []
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"tsconfig.app.json",
"tsconfig.spec.json",
"e2e/tsconfig.json"
],
"exclude": [
"**/node_modules/**"
]
}
},
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "angularRoot:serve"
},
"configurations": {
"production": {
"devServerTarget": "angularRoot:serve:production"
}
}
}
}
}
},
"defaultProject": "angular"
}
- 啟動嘗試加載
哇咔咔!!! 報錯。。。
- 解決方式
- 主應用中安裝
, 并且在zoom.js
之前引入import qiankun
- 将微應用的
裡面的引入src/polyfills.ts
zone.js
- 微應用
src/index.html
中引入<head>
zone.js
- 主應用中安裝
- 再次啟動嘗試加載
哇咔咔!!! 又報錯了。。。 什麼鬼,頁面倒是加載出來了,但是報了一串紅
查閱資料,貌似是熱更新的啊。 這裡不做過多解釋,暴力解決方案:
bug
。
作為子應用時不使用熱更新
-
=>package.json
中增加如下指令:script
作為微應用時使用: ng serve:qiankuan
啟動加載
build 報錯問題: 修改 tsconfig.json
檔案
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"module": "esnext",
"moduleResolution": "node",
"importHelpers": true,
"target": "es5",
"typeRoots": ["node_modules/@types"],
"lib": ["es2018", "dom"]
},
"angularCompilerOptions": {
"fullTemplateTypeCheck": true,
"strictInjectionParameters": true
}
}
- 檢視加載效果
應用間通信
多個應用間通信,這裡舉個簡單的例子:主應用中登入擷取使用者,當加載微應用時,微應用需要根據不同的使用者
id
展示不同的資料或者展示不同的頁面。這個時候就需要主應用中把對應的使用者
id
傳到微應用中去。傳值方式,這裡總結了三種方式:
id
- 挂載微應用時直接
傳值props
-
定義全局狀态initGlobalState
- 定義全局的狀态池
props 傳值
注冊微應用的基礎配置資訊時,增加 props
,傳入微應用需要的資訊
{
name: 'vue2',
entry: 'http://localhost:8001',
container: '#subContainer',
activeRule: '/vue2',
//props
props: {
id: 'props基礎傳值方式'
},
loader,
}
微應用中在生命周期
mount
中擷取
props
export async function mount(props) {
console.log('擷取主應用傳值',props)
render(props);
}
initGlobalState (推薦)
定義全局狀态,并傳回通信方法,建議在主應用使用,微應用通過 props
擷取通信方法。
- 主應用中聲明全局狀态
// 全局狀态
const state = {
id: 'main_主應用',
};
// 初始化 state
const actions: MicroAppStateActions = initGlobalState(state);
// 監聽狀态變更
actions.onGlobalStateChange((state, prev) => {
// state: 變更後的狀态; prev 變更前的狀态
console.log(state, prev);
});
- 微應用擷取通信,同樣在
生命周期中擷取mount
export async function mount(props) {
console.log('initGlobalState傳值',props)
render(props);
}
列印出來發現好像并沒有我們需要的值:
我想在這裡,細心的同學應該會發現,好像有個,
onGlobalStateChange
這兩個方法,見名知意,應該是用來做狀态的監聽和修改使用的。不管什麼神仙,先調用下試試看喽
setGlobalState
封裝一個 storeTest
方法做統一調用
function storeTest(props) {
props.onGlobalStateChange &&
props.onGlobalStateChange(
(value, prev) => console.log(`[onGlobalStateChange - ${props.name}]:`, value, prev),
true,
);
// 為了示範效果明顯增加定時器
setTimeout(() =>{
props.setGlobalState &&
props.setGlobalState({
id: `${props.name}_子應用`
});
},3000)
}
export async function mount(props) {
storeTest(props);
render(props);
}
輸出兩次 ???
輸出兩次的原因是在中調用
微應用
, 主應用中的
setGlobalState
也會執行
onGlobalStateChange
- 總結下
-
初始化initGlobalState
state
-
監聽狀态變更onGlobalStateChange
-
修改狀态setGlobalState
-
移除監聽offGlobalStateChange
- 問題
如果想在微應用某個頁面内修改全局狀态應該怎麼做 ? 當然是可以把 props
中的方法挂載到目前應用的全局上啦。例如:
export async function mount(props) {
storeTest(props);
render(props);
// 挂載到全局 instance 上
instance.config.globalProperties.$onGlobalStateChange = props.onGlobalStateChange;
instance.config.globalProperties.$setGlobalState = props.setGlobalState;
}
定義全局的狀态池
定義全局狀态池,說白了就是在主應用中定義全局狀态,可以使用
redux
等來定義。定義好全局狀态,可以定義一個全局的類,類中聲明兩個方法,一個用來擷取全局狀态,一個用來修改全局狀态。定義好之後,把這個類通過第一種
vuex
的傳值方式傳入,微應用通過
props
=>
mount
接收。這種方式就不做示範,個人建議使用第二種方式。
props
總結
到這裡,基于的微前端搭建基本完成。本文隻是對qiankun從0搭建到搭建過程中遇到問題并且解決問題以及後期項目中的一些基礎配置和使用做簡單概述。下一次将會對
qiankun
問題做個詳細概述。
多應用部署
源碼位址
https://github.com/xushanpei/qiankun_template
最後
如果覺得本文對你有幫助,希望能夠給我點贊支援一下哦 💪
也可以關注wx公衆号:
前端開發愛好者
回複加群,一起學習前端技能
公衆号内包含很多
vue react 實戰
精選資源教程,歡迎關注