微前端框架-乾坤的實際落地使用方式
微前端是什么?
- 一個針對前端大型,超大型專案遇到專案運維,開發瓶頸的解決方案
微前端針對誰?
- 大型或者超大型前端專案
怎么理解微前端?
- 與你當前技術無關,比如:vue,react,angular,jquery 等等,混著用都行,都沒關系
- 將一個大型專案,以模塊化思維進行拆分,獨立開發,獨立部署,獨立運行,最終由主框架同步更新
- 增量升級,對于大型專案,快速迭代,專案體積臃腫,無論是打包,還是發布,都不靈活,微前端,自應用可以獨立部署,靈活度大大提升
- 獨立運行,狀態隔離,狀態不共享等
設計思路
- 整個專案既然龐大,那就把各個模塊拆開,化整為零,模塊獨立自治包括開發?部署?等運維操作等
- 最終把多個模塊拼裝起來,放到一個公共的容器中,只要讓各個模塊功能打通即可
- 所以架構基調,一主,多從,
乾坤框架
來自阿里體系的微前端解決方案:https://qiankun.umijs.org/zh
為什么選擇乾坤?
- 基于 single-spa 封裝而來,但解決了 single 的弊端,如全域樣式混亂,全域變數污染,
- 易用,成熟,可靠案例多,大廠嫡系解決方案,穩定性 ok
- 包含了樣式隔離,js 沙箱,預加載等一些列生產解決方案
怎么用?
- 一個主專案,多個子專案
- 所以先搭建一個基座
- 再創建子應用
- 將子應用跟主應用關聯起來
- 將各個子應用關聯起來
所以在學什么?
- 配置,主專案和從專案的具體配置
- 原理,為什么這么配?背后到底干了什么
- 實際應用
代碼倉庫
git@github.com:sunhailiang/my-qiankun-demo.git
先配置主應用(基座)
具體步驟
一、主專案安裝乾坤
npm i qiankun -S
二、src 目錄下新增一個 registApp.js 專門用來注冊子應用
// 注冊app
import { registerMicroApps, start } from "qiankun";
// 加載狀態
const loader = (loadding) => {
console.log(loadding);
};
const apps = [
{
name: "one",
entry: "http://192.168.8.117:8081/", // 根據地址加載子應用,決議js,所以子應用必須支持跨域,讓我主應用可以加載的到
container: "#container", // 將子應用掛載到哪個元素?
activeRule: "/one", // 激活子應用的路由
loader, // 加載狀態
},
{
name: "two",
entry: "http://192.168.8.117:8082/",
container: "#container",
activeRule: "/two",
loader,
},
{
name: "vue3",
entry: "http://192.168.8.117:8083/",
container: "#container",
activeRule: "/vue3",
loader,
},
];
// 注冊子應用,并且暴露對應的生命周期鉤子,你可以根據需求寫邏輯
registerMicroApps(apps, {
//加載前
beforeLoad: (params) => {
console.log("加載前", params);
},
//掛載前
beforeMount: (params) => {
console.log("掛載前", params);
},
// 掛載后
afterMount: (params) => {
console.log("掛載后", params);
},
// 卸載前
beforeUnmount: (params) => {
console.log("卸載前", params);
},
// 卸載后
afterUnmount: (params) => {
console.log("卸載后", params);
},
});
// 開啟
start({
sandbox: {
// experimentalStyleIsolation: true, // 添加掛載容器,你可以理解為scoped
strictStyleIsolation: true, // showDom思路,完全隔離dom,無法互相訪問
},
});
三、改造 app.vue
<div id="app">
<el-menu :router="true" mode="horizontal">
<!-- 基座-放置主框架自己的東西 -->
<el-menu-item index="/">主頁</el-menu-item>
<el-menu-item index="/one">子應用</el-menu-item>
<el-menu-item index="/two">子應用</el-menu-item>
</el-menu>
<router-view />
<div id="container"></div>
</div>
四、main.js 中引入 registApp.js
五、運行所有子專案
六、切換主專案的路由------>跨域了吧???
配置子應用
1、首先要做的就是打包子應用生命周期,并且暴露出去
2、改寫子專案的組態檔,使子專案的資源始終用的是自己服務器上的 資源而不是用的主框架的資源(需要理解微前端的實作原理)
新建 vue.config.js
module.exports = {
publicPath: "//localhost:8081", // 靜態資源指回原有自己的服務器,而不是被主框架讀過去后用了主框架的靜態資源
devServer: {
port: "8081",
headers: {
"Access-Control-Allow-Origin": "*", // 允許主框架讀取資源
},
},
configureWebpack: {
// 打包檔案出口
output: {
libraryTarget: "umd", //打包成umd格式(相比于cmd,amd規范,umd能兼容他倆,所以做通用模塊,而且主框架通過featch(promise)方式獲取子應用可以直接決議umd模塊)
library: "part-one", // 資源命名
},
},
};
暴露乾坤要的 API->改寫 main.js
import Vue from "vue";
import App from "./App.vue";
import routes from "./router";
import store from "./store";
import VueRouter from "vue-router";
import actions from "./actions";
Vue.config.productionTip = false;
// 確保主專案切到子專案路由時再去掛載專案
let router = null;
let app = null;
const isQiankun = window.__POWERED_BY_QIANKUN__;
function render(props = {}) {
if (props) {
actions.setActions(props);
}
const { container } = props;
router = new VueRouter({
base: isQiankun ? "/one/" : "/", // 如果是在主專案中被加載應用時,則改成跟主應用一樣的base路由,否則,就按照本地的規則
mode: isQiankun ? "history" : "hash",
routes,
});
app = new Vue({
store,
router,
render: (h) => h(App),
}).$mount(container ? container.querySelector("#app") : "#app"); //存在乾坤容器嗎?有就用,沒有就用自己的
}
//獨立運行,__POWERED_BY_QIANKUN__這個屬性是乾坤暴露在windows上,判斷當前的應用運行環境,如果不在乾坤中則獨立運行,否則那就在乾坤之行鉤子函式時執行渲染
if (!isQiankun) {
render();
}
// 必須暴露這些鉤子函式
export async function bootstrap() {
console.log("one app bootstraped");
}
export async function mount(props) {
//在自應用掛載時,會將乾坤的容器等引數塞進來
render(props);
}
// 子應用卸載時,將不用的物件全部銷毀
export async function unmount() {
console.log("one app unmount");
app.$destroy();
app.$el.innerHTML = "";
router = null;
app = null;
}
樣式隔離
子應用之間樣式隔離
- 默認采用的動態樣式表,加載時添加樣式,卸載時洗掉樣式
主從之間的隔離怎么處理?
- 人力干預:BEM(不推薦)
- css-moudles:動態生成前綴(不推薦-也不能完全隔離)
- 乾坤提供了一個試驗性樣式隔離
主應用 registApp.js,給乾坤的 start 函式注入一個物件
- 還有一個 shadowDom 的隔離方式
// 開啟
start({
sandbox: {
// experimentalStyleIsolation: true, // 添加掛載容器,你可以理解為scoped,密閉性一般,但思路簡單
strictStyleIsolation: true, // shadowDom思路,完全隔離dom,無法互相訪問
},
js 隔離
- 支持 proxy 時
- 單例:legacySandBox,
- 多例:proxySandBox
- 不支持 proxy 時
- 降級->napshotSandBox,
預加載方案
主動預先加載指定子專案的靜態資源,對于靜態資源體積過大的自應用可以直接采用改方案
主應用/registApp.js
start({prefetch:true|all|[]|()=>{}})默認開啟:prefetch:true
默認啟用子應用
主應用/registApp.js 中從乾坤決議出 setDefaultMountApp 方法
import {setDefaultMountApp } from "qiankun";setDefaultMountApp("/vue3");
應用通信(Actions-官方推薦)-Micro-part-one
Actions 通信(官方示例)
-
核心知識點:觀察者模式
-
優點:
- 使用簡單;
- 官方支持性高;
- 適合通信較少的業務場景;
-
缺點:
- 子應用獨立運行時,需要額外配置無 Actions 時的邏輯;
- 子應用需要先了解狀態池的細節,再進行通信;
- 由于狀態池無法跟蹤,通信場景較多時,容易出現狀態混亂、維護困難等問題;
-
先看圖示意

主應用配置
- 跟目錄或指定目錄新增一個 actions.js
import { initGlobalState, MicroAppStateActions } from "qiankun";
// 注意:MicroAppStateActions 是個型別,在ts中要做型別判斷
// 應用通信
const initState = {
// 宣告你需要共享/需要雙向通信的資料
main: "",
};
const actions = initGlobalState(initState);
export default actions;
在組件內參考 actions.js 并使用
import actions from "./actions";
methods: {
setval() {
let main = this.main;
// 將資料傳給子應用
actions.setGlobalState({ main });
},
},
mounted() {
// 組件掛在是監聽state(包括自己/子應用傳過來的,雙向通信)
actions.onGlobalStateChange((state, preState) => {
console.log("當前值", state);
console.log("上一個值", preState);
this.main = state.main;
});
},
主組件輸入時將資料同步給子應用
<el-input @input="setval" v-model="main"></el-input>
子應用配置
- 跟目錄或者其他目錄新建 actions.js
function emptyAction() {
console.warn("Current execute action is empty!");
}
class Actions {
actions = {
onGlobalStateChange: emptyAction,
setGlobalState: emptyAction,
};
setActions(actions) {
this.actions = actions;
}
onGlobalStateChange(...args) {
// 映射方法
return this.actions.onGlobalStateChange(...args);
}
setGlobalState(...args) {
映射方法
return this.actions.setGlobalState(...args);
}
}
const actions = new Actions();
export default actions;
將映射方法通過 render 注入到乾坤框架
- main.js
import actions from "./actions";
// render函式中
function render(props = {}) {
if (props) {
actions.setActions(props);
}
}
子應用組件中
- 輸入框:
<input v-model="main" @input="setState()" />
import actions from "../actions";
methods:{
setState() {
const main = this.main;
actions.setGlobalState({ main }); // 子應用同步給父級應用
},
},
mounted() {
// 監聽資料
actions.onGlobalStateChange((state) => {
const { main } = state;
this.main = main;
});
},
應用通信(Shared-基于 vuex/redux 等第三方)-Micro-part-two
-
優點:
- 子應用無法隨意污染主應用的狀態池,只能通過主應用暴露的 shared 實體的特定方法操作狀態池,從而避免狀態池污染產生的問題,
- 子應用將具備獨立運行的能力
-
看圖示意
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-LIW0ZnWI-1641285126287)(./micro-main/src/assets/imgs/store.jpg)]](https://img.uj5u.com/2022/01/05/295126052139002.jpg)
-
本質上就是共享一個 store 你懂我意思吧??就是把主專案 store 讓各個子專案共享
改造主專案
- registApp.js
- 引入 store
import store from "./store";
- 引入 store
- 將主專案 store 以引數形式傳給指定子專案
{
name: "two",
entry: "http://192.168.8.117:8082/",
container: "#container",
activeRule: "/two",
loader,
props: {
mainStore: store, // 將主專案的store注入到子專案中
},
},
- 給 store 添加一個測驗資料
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
token: "123456abc", // 共享資料
},
mutations: {
setToken(state, val) {
// 變更state
state.token = val;
},
},
});
- 組件內,監聽一下 state 資料
watch: {
// vuex方案實時監聽
"$store.state.token": function(val, pre) {
console.log("監聽", val, "pre", pre);
this.main = val;
},
},
- 修改組件事件內容
methods: {
setval() {
let main = this.main;
// vuex方案
this.$store.commit("setToken", main);
},
},
修改子專案
- main.js
- render 函式中獲取主專案的 store
function render(props = {}) {
// 傳入主專案store
const { container, mainStore } = props;
}
// 如果有props
if (props) {
// 為主專案的store注冊回應式
Vue.observable(mainStore);
// 將主專案store擴展到子專案
Vue.prototype.$mainStore = mainStore;
}
- 雙向同步資料
<!-- 直接系結主專案state通信完成 -->
<input v-model="$mainStore.state.token" />
還有其他通信方案嗎?
- 有,比如 eventBus
公共組件庫
- npm 私域即可
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/403981.html
標籤:其他
