整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          探探各個微前端框架

          探探各個微前端框架

          前端架構是為了在解決單體應用在一個相對長的時間跨度下,由于參與的人員、團隊的增多、變遷,從一個普通應用演變成一個巨石應用(Frontend Monolith)后,隨之而來的應用不可維護的問題。這類問題在企業級 Web 應用中尤其常見。
          微前端框架內的各個應用都支持獨立開發部署、不限技術框架、支持獨立運行、應用狀態隔離但也可共享等特征。
          本文會從框架的應用隔離實現方案、實戰、優缺點三個方面探一探各個框架。幫助大家了解各個框架是如何使用,如何運行,從而能選出適合自己項目的微前端方案。

          iframe

          在沒有各大微前端解決方案之前,iframe是解決這類問題的不二之選,因為iframe提供了瀏覽器原生的硬隔離方案,不論是樣式隔離、js 隔離這類問題統統都能被完美解決。
          但他的最大問題也在于他的隔離性無法被突破,導致應用間上下文無法被共享,隨之帶來的開發體驗、產品體驗的問題:

          1. url 不同步,瀏覽器刷新 iframe url 狀態丟失、后退前進按鈕無法使用。
          2. UI 不同步,DOM 結構不共享,彈窗只能在iframe內部展示,無法覆蓋全局
          3. 全局上下文完全隔離,內存變量不共享,iframe 內外系統的通信、數據同步等需求,主應用的 cookie 要透傳到根域名都不同的子應用中實現免登效果。
          4. ,每次子應用進入都是一次瀏覽器上下文重建、資源重新加載的過程。

          single-spa

          目前(2024年4月)github star 13k
          Single-spa(https://github.com/single-spa/single-spa) 是最早的微前端框架,兼容多種前端技術棧;是一個將多個單頁面應用聚合為一個整體應用的 JavaScript 微前端框架;
          簡單來說就是一個聚合,使用這個庫可以讓你的應用可以 使用多個不同的技術棧(vue、react、angular等等)進行同步開發,最后使用一個公用的路由去實現完美的切換;

          實現方案

          Single-spa 實現了一套生命周期,開發者需要在相應的時機自己去加載對應的子應用。
          它做的事情就是
          注冊子應用、監聽 URL 變化,然后加載對應的子應用js,執行對應子應用的生命周期流程。

          1. 提供registerApplication方法,用來注冊子應用列表。
          2. 提供了activeWhen,由開發者指定路由滿足條件時,激活(掛載)子應用的js、css。
          3. js隔離由single-spa-leaked-globals實現,本質上就是在 mount A 子應用時,正常添加全局變量,比如 jQuery 的 $, lodash 的 _。在 unmount A 子應用時,用一個對象記錄之前給 window 添加的全局變量,并把 A 應用里添加 window 的變量都刪掉。下一次再 mount A 應用時,把記錄的全局變量重新加回來就好了。
          4. css隔離:子應用和子應用之間通過single-spa-css插件提供的css生命周期函數,做到子應用mount時加載css,子應用unmount時將css也unmount掉;而主應用與子應用之間可以通過PostCSSPrefix Selector 給樣式自動加前綴的方式,或者Shadow DOM 的形式去解決。

          single-spa實戰

          1. 主應用入口文件:

          主要通過single-spa提供的registerApplication方法注冊子應用,子應用需要指定加載子應用的方法、和路由條件。

          import Vue from 'vue'
          import App from './App.vue'
          import router from './router'
          import { registerApplication, start } from 'single-spa'
          
          Vue.config.productionTip=false
          
          // 遠程加載子應用
          function createScript(url) {
            return new Promise((resolve, reject)=> {
              const script=document.createElement('script')
              script.src=url
              script.onload=resolve
              script.onerror=reject
              const firstScript=document.getElementsByTagName('script')[0]
              firstScript.parentNode.insertBefore(script, firstScript)
            })
          }
          
          // 記載函數,返回一個 promise
          function loadApp(url, globalVar) {
            // 支持遠程加載子應用
            return async ()=> {
              await createScript(url + '/js/chunk-vendors.js')
              await createScript(url + '/js/app.js')
              // 這里的return很重要,需要從這個全局對象中拿到子應用暴露出來的生命周期函數
              return window[globalVar]
            }
          }
          
          // 子應用列表
          const apps=[
            {
              // 子應用名稱
              name: 'app1',
              // 子應用加載函數,是一個promise
              app: loadApp('http://localhost:8081', 'app1'),
              // 當路由滿足條件時(返回true),激活(掛載)子應用
              activeWhen: location=> location.pathname.startsWith('/app1'),
              // 傳遞給子應用的對象
              customProps: {}
            },
            {
              name: 'app2',
              app: loadApp('http://localhost:8082', 'app2'),
              activeWhen: location=> location.pathname.startsWith('/app2'),
              customProps: {}
            },
            {
              // 子應用名稱
              name: 'app3',
              // 子應用加載函數,是一個promise
              app: loadApp('http://localhost:3000', 'app3'),
              // 當路由滿足條件時(返回true),激活(掛載)子應用
              activeWhen: location=> location.pathname.startsWith('/app3'),
              // 傳遞給子應用的對象,這個很重要,該配置告訴react子應用自己的容器元素是什么,這塊兒和vue子應用的集成不一樣,官網并沒有說這部分,或者我沒找到,是通過看single-spa-react源碼知道的
              customProps: {
                domElement: document.getElementById('microApp'),
                // 添加 name 屬性是為了兼容自己寫的lyn-single-spa,原生的不需要,當然加了也不影響
                name: 'app3'
              }
            }
          ]
          
          // 注冊子應用
          for (let i=apps.length - 1; i >=0; i--) {
            registerApplication(apps[i])
          }
          
          new Vue({
            router,
            mounted() {
              // 啟動
              start()
            },
            render: h=> h(App)
          }).$mount('#app')
          
          

          2. 子應用導出文件

          • 子應用需要安裝single-spa-react或者single-spa-vue,
          • 將子應用傳遞給single-spa-react,得到子應用運行的生命周期,
          • 子應用將生命周期導出到全局,
          • 在主應用可以獲取子應用的生命周期函數
          import React from 'react';
          import ReactDOM from 'react-dom';
          import './index.css'
          import { BrowserRouter, Link, Route } from 'react-router-dom'
          import singleSpaReact from 'single-spa-react'
          
          // 子應用獨立運行
          if (!window.singleSpaNavigate) {
            ReactDOM.render(rootComponent(), document.getElementById('root'))
          }
          
          // 生命周期a
          const reactLifecycles=singleSpaReact({
            React,
            ReactDOM,
            rootComponent,
            errorBoundary(err, info, props) {
              return <div>
                This renders when a catastrophic error occurs
              </div>
            }
          })
          
          // 這里和vue不一樣,props必須向下傳遞
          export const bootstrap=async props=> {
            console.log('app3 bootstrap');
            return reactLifecycles.bootstrap(props)
          }
          export const mount=async props=> {
            console.log('app3 mount');
            return reactLifecycles.mount(props);
          }
          export const unmount=async props=> {
            console.log('app3 unmount');
            return reactLifecycles.unmount(props)
          }
          
          // 根組件
          function rootComponent() {
            return <React.StrictMode>
              <BrowserRouter>
                <div>
                  <Link to="/app3">Home</Link> |
                    <Link to="/app3/about"> About</Link>
                  <Route exact path="/app3" component={Home} />
                  <Route exact path="/app3/about" component={About} />
                </div>
              </BrowserRouter>
            </React.StrictMode>
          }
          
          // home 組件
          function Home() {
            return <div>
              <h1>app3 home page</h1>
            </div>
          }
          
          // about 組件
          function About() {
            return <div>
              <h1>app3 about page</h1>
            </div>
          }
          

          3. 打包配置

          將子應用導出模式設置為umd

          const package=require('./package.json')
          module.exports={
            // 告訴子應用在這個地址加載靜態資源,否則會去基座應用的域名下加載
            publicPath: '//localhost:8082',
            // 開發服務器
            devServer: {
              port: 8082
            },
            configureWebpack: {
              // 導出umd格式的包,在全局對象上掛載屬性package.name,基座應用需要通過這個全局對象獲取一些信息,比如子應用導出的生命周期函數
              output: {
                // library的值在所有子應用中需要唯一
                library: package.name,
                libraryTarget: 'umd'
              }
            }
          }
          

          4. 預覽


          可以看到它是動態加載的子應用的js,并執行js,將內容渲染到了主應用的盒子內。

          框架優缺點

          優點:

          • 敏捷性 - 獨立開發、獨立部署,微應用倉庫獨立,前后端可獨立開發,部署完成后主框架自動完成同步更新;
          • 技術棧無關,主框架不限制接入應用的技術棧,微應用具備完全自主權;
          • 增量升級,在面對各種復雜場景時,我們通常很難對一個已經存在的系統做全量的技術棧升級或重構,而微前端是一種非常好的實施漸進式重構的手段和策略

          缺點

          • 需要自己去加載子應用
          • 不支持 Javascript 沙箱隔離,需要自己去使用single-spa-leaked-globals之類的庫去隔離
          • 不支持css隔離,需要自己使用single-spa-css庫或者postcss等去解決樣式沖突問題
          • 無法預加載

          qiankun

          目前(2024年4月) github star 15.4k
          阿里的
          qiankun 是一個基于 single-spa 的微前端實現庫,孵化自螞蟻金融,幫助大家能更簡單、無痛的構建一個生產可用微前端架構系統。

          實現方案

          • single-spa是基于js-entry方案,而qiankun 是基于html-entry 及沙箱設計,使得微應用的接入 像使用 iframe 一樣簡單
          • 主應用監聽路由,加載對應子應用的html,掛載到主應用的元素內,然后解析子應用的html,從中分析出css、js再去沙盒化后加載執行,最終將子應用的內容渲染出來。
          • qiankun實現樣式隔離有兩種模式可供開發者選擇:
            • strictStyleIsolation 這種模式下 qiankun 會為每個微應用的容器包裹上一個 shadow dom 節點,從而確保微應用的樣式不會對全局造成影響。
            • experimentalStyleIsolation 當 experimentalStyleIsolation 被設置為 true 時,qiankun 會改寫子應用所添加的樣式,會為所有樣式規則增加一個特殊的選擇器規則,來限定其影響范圍
          • qiankun實現js隔離,采用了兩種沙箱,分別為基于Proxy實現的沙箱快照沙箱,當瀏覽器不支持Proxy會降級為快照沙箱

          Proxy沙箱機制:

          // 偽代碼
          class ProxySandbox {
              constructor() {
                  const rawWindow=window;
                  const fakeWindow={}
                  const proxy=new Proxy(fakeWindow, {
                      set(target, p, value) {
                          target[p]=value;
                          return true
                      },
                      get(target, p) {
                          return target[p] || rawWindow[p];
                      }
                  });
                  this.proxy=proxy
              }
          }
          let sandbox1=new ProxySandbox();
          let sandbox2=new ProxySandbox();
          window.a=1;
          // 偽代碼
          ((window)=> {
              window.a='hello';
              console.log(window.a) // hello
          })(sandbox1.proxy);
          ((window)=> {
              window.a='world';
              console.log(window.a) // world
          })(sandbox2.proxy);
          

          快照沙箱

          // 偽代碼
          class SnapshotSandbox {
              constructor() {
                  this.proxy=window; 
                  this.modifyPropsMap={}; // 修改了那些屬性
                  this.active(); // 調用active保存主應用window快照
              }
          
              /**1. 初始化時,在子應用即將mount前,先調用active,保存當前主應用的window快照*/
              active() {
                  this.windowSnapshot={}; // window對象的快照
                  for (const prop in window) {
                      if (window.hasOwnProperty(prop)) {
                          // 將window上的屬性進行拍照
                          this.windowSnapshot[prop]=window[prop];
                      }
                  }
                  Object.keys(this.modifyPropsMap).forEach(p=> {
                      window[p]=this.modifyPropsMap[p];
                  });
              }
          
              /**
              * 子應用卸載時,遍歷當前子應用的window屬性,和主應用的window快照做對比
              * 如果不一致,做兩步操作 
              *     1. 保存 不一致的window屬性,
              *     2. 還原window
              */
              inactive() {
                  for (const prop in window) { // diff 差異
                      if (window.hasOwnProperty(prop)) {
                          // 將上次拍照的結果和本次window屬性做對比
                          if (window[prop] !==this.windowSnapshot[prop]) {
                              // 保存修改后的結果
                              this.modifyPropsMap[prop]=window[prop]; 
                              // 還原window
                              window[prop]=this.windowSnapshot[prop]; 
                          }
                      }
                  }
              }
          }
          

          qiankun實戰

          1. 主應用入口文件

          初始化主應用,并注冊子應用

          主應用入口文件初始化應用,注冊子應用,注冊子應用時支持傳入子應用列表, 注冊子應用時需要指明以下幾個主要參數:

          1. name: 微應用的名稱,微應用之間必須確保唯一
          2. entry: 子應用的訪問鏈接。主應用會加載整個頁面,例如https://qiankun.umijs.org/guide/
          3. container:需要掛載子應用的DOM元素
          4. loader: 子應用未加載時的界面,一般為loading
          5. activeRule: 路由匹配規則

          開啟子應用start(options)

          options.prefetch此時可以選擇是否預加載子應用。
          options.sandbox默認情況下的沙箱可以確保單實例場景子應用之間的樣式隔離,但是無法確保主應用跟子應用、或者多實例場景的子應用樣式隔離。qiankun提供了另外兩種方式的隔離,供開發者選擇:

          • strictStyleIsolation: 當配置為 { strictStyleIsolation: true } 時表示開啟嚴格的樣式隔離模式。這種模式下 qiankun 會為每個微應用的容器包裹上一個 shadow dom 節點,從而確保微應用的樣式不會對全局造成影響。
          • experimentalStyleIsolation:當 {experimentalStyleIsolation: true} 被設置,qiankun 會改寫子應用所添加的樣式為所有樣式規則增加一個特殊的選擇器規則來限定其影響范圍。
          import { registerMicroApps, start, initGlobalState } from 'qiankun';
          
          registerMicroApps([
            {
              name: 'react app', // app name registered
              entry: '//localhost:7100',
              container: '#yourContainer',
              activeRule: '/yourActiveRule',
            },
            {
              name: 'vue app',
              entry: { scripts: ['//localhost:7100/main.js'] },
              container: '#yourContainer2',
              activeRule: '/yourActiveRule2',
            },
          ]);
          
          // 通訊
          const { onGlobalStateChange, setGlobalState }=initGlobalState({
            user: 'qiankun',
          });
          
          onGlobalStateChange((value, prev)=> console.log('[onGlobalStateChange - master]:', value, prev));
          
          setGlobalState({
            ignore: 'master',
            user: {
              name: 'master',
            },
          });
          
          /**
           * 設置默認進入的子應用
           */
          setDefaultMountApp('/react16');
          
          /**
           * 啟動應用
           */
          start({
            prefetch: true, // 預加載子應用
            sandbox:{
               strictStyleIsolation: true, //  shadow dom的方式實現樣式隔離
              // experimentalStyleIsolation: true, //添加特殊的選擇器的方式實現樣式隔離
            }
          });
          
          runAfterFirstMounted(()=> {
            console.log('[MainApp] first app mounted');
          });
          
          

          2. 子應用導出生命周期鉤子

          子應用需要在自己的入口 js導出 bootstrap、mount、unmount 三個生命周期鉤子,以供主應用在適當的時機調用。

          import React from 'react';
          import ReactDOM from 'react-dom';
          import App from './App';
          import * as serviceWorker from './serviceWorker';
          
          function render(props) {
            const { container }=props;
            ReactDOM.render(<App />, container ? container.querySelector('#root') : document.querySelector('#root'));
          }
          
          /**
          * 和主應用通訊
          */
          function storeTest(props) {
            props.onGlobalStateChange((value, prev)=> console.log(`[onGlobalStateChange - ${props.name}]:`, value, prev), true);
            props.setGlobalState({
              ignore: props.name,
              user: {
                name: props.name,
              },
            });
          }
          
          
          if (!window.__POWERED_BY_QIANKUN__) {
            render({});
          }
          
          /**
           * bootstrap 只會在微應用初始化的時候調用一次,下次微應用重新進入時會直接調用 mount 鉤子,不會再重復觸發 bootstrap。
           * 通常我們可以在這里做一些全局變量的初始化,比如不會在 unmount 階段被銷毀的應用級別的緩存等。
           */
          export async function bootstrap() {
            console.log('[react16] react app bootstraped');
          }
          
          /**
           * 應用每次進入都會調用 mount 方法,通常我們在這里觸發應用的渲染方法
           */
          export async function mount(props) {
            console.log('[react16] props from main framework', props);
            storeTest(props);
            render(props);
          }
          
          /**
           * 應用每次 切出/卸載 會調用的方法,通常在這里我們會卸載微應用的應用實例
           */
          export async function unmount(props) {
            const { container }=props;
            ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root'));
          }
          

          3. 配置打包工具:

          為了讓主應用能正確識別微應用暴露出來的一些全局信息和開發環境下的跨域兼容,在子應用(以create-react-app出來的react項目為例)安裝@rescripts/cli,并在子應用目錄下新建.rescriptsrc.js,內容如下:

          const { name }=require('./package');
          
          module.exports={
            webpack: (config)=> {
              config.output.library=`${name}-[name]`;
              config.output.libraryTarget='umd'; // 為了能通過window['app-name1']拿到子應用聲明的生命周期
              // webpack 5 需要把 jsonpFunction 替換成 chunkLoadingGlobal
              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;
            },
          };
          

          4. 預覽

          使用strictStyleIsolation:true方式進行樣式隔離,會生成一個shadow dom,進行樣式的完全隔離:

          使用experimentalStyleIsolation:true的方式進行樣式隔離,會在css選擇器前添加特殊標識:

          可以看到,qiankun會將子應用的html渲染到自定義的container中。 主應用加載的是子應用的html,在解析子應用的html的過程中遇到js和css會載框架內進行沙盒處理,完成css和js的隔離,之后下載并執行,完成整個子應用的渲染過程。

          框架優缺點

          優點

          • html entry的接入方式,不需要自己寫load方法,而是直接寫子應用的訪問鏈接就可以。
          • 提供js沙箱
          • 提供樣式隔離,兩種方式可選
          • 資源預加載,在瀏覽器空閑時間預加載未打開的微應用資源,加速微應用打開速度。
          • 社區活躍
          • umi 插件,提供了 @umijs/plugin-qiankun 供 umi 應用一鍵切換成微前端架構系統 除了最后一點拓展以外,微前端想要達到的效果都已經達到。
          • 應用間通信簡單,全局注入
          • 路由保持,瀏覽器刷新、前進、后退,都可以作用到子應用

          缺點

          • 改造成本較大,從 webpack、代碼、路由等等都要做一系列的適配
          • 對 eval 的爭議,eval函數的安全和性能是有一些爭議的:MDN的eval介紹;
          • 無法同時激活多個子應用,也不支持子應用保活
          • 無法支持 vite 等 ESM 腳本運行

          wujie

          目前(2024年4月)github star 3.7k
          wujie是騰訊出品。基于
          webcomponent 容器 + iframe 沙箱,能夠完善的解決適配成本、樣式隔離、運行性能、頁面白屏、子應用通信、子應用保活、多應用激活、vite 框架支持、應用共享等

          實現方案

          • 無界利用iframe和webcomponent來搭建天然的js隔離沙箱css隔離沙箱,
          • 利用iframe的history主應用的history在同一個top-level browsing context來搭建天然的路由同步機制
          • 支持以fiber的形式執行js,由于子應用的執行會阻塞主應用的渲染線程,當fiber設置為true,那么js執行時采取類似react fiber的模式方式間斷執行,每個 js 文件的執行都包裹在requestidlecallback中,每執行一個js可以返回響應外部的輸入,但是這個顆粒度是js文件,如果子應用單個js文件過大,可以通過拆包的方式降低達到fiber模式效益最大化

          wujie是如何渲染子應用的?

          wujie跟qiankun一樣,都是基于html entry加載的,但他們解析html的過程是不一樣的。 qiankun是直接解析并執行js、css、html的,而wujie則是先解析html,提取出script腳本放入空的iframe中,提取出css、html放入到web components中,具體來說:

          1. 解析入口 HTML ,分別得到script、css、模版html
          2. 創建一個純凈的 iframe,為了實現應用間(iframe 間)通訊,無界子應用 iframe 的 url 會設置為主應用的域名(同域),因此 iframe 的 location.href 并不是子應用的 url。創建好后停止加載iframe。
          3. iframe內插入js,將抽離出來的script腳本,插到iframe中去,在iframe中執行子應用的js
          4. 創建web component,id為子應用id,將抽離出來的html插入。
          5. 由于iframe內的js有可能操作dom,但是iframe內沒有dom,隨意wujie框架內對iframe攔截document對象,統一將dom指向shadowRoot,此時比如新建元素、彈窗或者冒泡組件就可以正常約束在shadowRoot內部。

          wujie實戰

          wujie接入很簡單,主應用可以讓開發者以組件的方式加載子應用。子應用只需要做支持跨域請求改造,這個是所有微前端框架運行的前提,除此之外子應用可以不做任何改造就可以在無界框架中運行,不過此時運行的方式是重建模式。 子應用也可以配置保活、生命周期適配進入保活模式或單例模式。

          1. 主應用入口文件

          與其他框架一樣,先配置子應用,

          // main-react/index.js
          import "react-app-polyfill/stable";
          import "react-app-polyfill/ie11";
          
          import React from "react";
          import ReactDOM from "react-dom";
          import WujieReact from "wujie-react";
          import "./index.css";
          import App from "./App";
          import hostMap from "./hostMap";
          import credentialsFetch from "./fetch";
          import lifecycles from "./lifecycle";
          import plugins from "./plugin";
          
          const { setupApp, preloadApp, bus }=WujieReact;
          const isProduction=process.env.NODE_ENV==="production";
          bus.$on("click", (msg)=> window.alert(msg));
          
          const degrade=window.localStorage.getItem("degrade")==="true" || !window.Proxy || !window.CustomElementRegistry;
          /**
           * 大部分業務無需設置 attrs
           * 此處修正 iframe 的 src,是防止github pages csp報錯
           * 因為默認是只有 host+port,沒有攜帶路徑
           */
          const attrs=isProduction ? { src: hostMap("//localhost:7700/") } : {};
          /**
           * 配置應用,主要是設置默認配置
           * preloadApp、startApp的配置會基于這個配置做覆蓋
           */
          setupApp({
            name: "react16",
            url: hostMap("//localhost:7600/"),
            attrs, // 子應用iframe的src
            exec: true, // 預執行
            fetch: credentialsFetch, // 自定義的fetch方法
            plugins,
            /** 子應用短路徑替換,路由同步時生效 */
            prefix: { "prefix-dialog": "/dialog", "prefix-location": "/location" },
             /** 子應用采用降級iframe方案 */
            degrade,
            ...lifecycles,
          });
          
          setupApp({
            name: "vue3",
            url: hostMap("//localhost:7300/"),
            attrs,
            exec: true,
            alive: true, // 子應用保活,state不會丟失
            plugins: [{ cssExcludes: ["https://stackpath.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"] }],
            // 引入了的第三方樣式不需要添加credentials
            fetch: (url, options)=>
              url.includes(hostMap("//localhost:7300/")) ? credentialsFetch(url, options) : window.fetch(url, options),
            degrade,
            ...lifecycles,
          });
          
          
          
          if (window.localStorage.getItem("preload") !=="false") {
            preloadApp({
              name: "react16",
            });
           
            if (window.Proxy) {
              preloadApp({
                name: "vue3",
              });
            }
          }
          
          ReactDOM.render(<App />, document.getElementById("root"));
          
          

          引入子應用的地方直接以組件式的方式引入:

          import React from "react";
          import hostMap from "../hostMap";
          import WujieReact from "wujie-react";
          import { useNavigate, useLocation } from "react-router-dom";
          
          export default function React16() {
            const navigation=useNavigate();
            const location=useLocation();
            const path=location.pathname.replace("/react16-sub", "").replace("/react16", "").replace("/",""); ////
            const react16Url=hostMap("//localhost:7600/") + path;
            const props={
              jump: (name)=> {
                navigation(`/${name}`);
              },
            };
            return (
              // 單例模式,name相同則復用一個無界實例,改變url則子應用重新渲染實例到對應路由
              <WujieReact
                width="100%"
                height="100%"
                name="react16"
                url={react16Url}
                sync={!path}
                props={props}
              ></WujieReact>
            );
          }
          
          

          2. 預覽

          框架優缺點

          優點

          • 接入簡單,可以以組件的方式引入子應用
          • 純凈無污染
            • 無界利用iframe和webcomponent來搭建天然的js隔離沙箱和css隔離沙箱
            • 利用iframe的history和主應用的history在同一個top-level browsing context來搭建天然的路由同步機制
            • 副作用局限在沙箱內部,子應用切換無需任何清理工作,沒有額外的切換成本
          • 支持vite esmoudle加載,由于js是獨立在iframe中加載的,所以支持esmodule加載
          • 支持預加載
          • 支持應用保活,子應用狀態保留,由于是獨立在iframe中的,而切換應用時不會移除iframe,所以子應用的狀態會被保留在原來的iframe中,當主應用再次渲染子應用dom時,會顯示之前的狀態。
          • 多應用同時激活在線

          缺點

          1. iframe沙箱的src設置了主應用的host,初始化iframe的時候需要等待iframe的location.orign從'about:blank'初始化為主應用的host,這個采用的計時器去等待的不是很優雅。

          Micro App

          截至目前(2024年4月)github star 5.2k
          mirco-app 是京東2021年開源的一款微前端框架。它借助了瀏覽器對 webComponent 的支持,實現了一套微前端方案體系。并且由于 Shadow Domreact 這類庫的兼容性較差,便自己實現了類 Shadow Dom 的效果。與 qiankun 相比,接入更加簡單。最新的版本也支持iframe實現js隔離,類似wujie

          實現方案

          首先micro-app實現了一個基于WebComponent的組件,并實現了類Shadow Dom 的效果,開發者只需要用<micro-app name="xx" url="xx" baseroute="/xxx/xxx">來加載子應用,整個對子應用的加載、js隔離、css隔離的邏輯都封裝在了web component組件<micro-app>中,具體來說:

          1. 當調用microApp.start()后,會注冊一個名為micro-app 的自定義 webComponent 標簽。我們可以從 <micro-app name='app1' url='xx' baseroute='/my-page'></micro-app> 中拿到子應用的線上入口地址。
          2. <micro-app>組件內部,當匹配到路由后,跟qiankun一樣加載html,得到html字符串模版
          3. 分析html字符串,提取<head>頭和<body>,并替換為框架自定義標簽<micro-app-head>和<micro-app-body>
          4. 在<micro-app-head>內,會對script標簽和link標簽的內容進行加載并執行
          5. 將<micro-app-head>和<micro-app-body>插入到<micro-app>標簽內
          6. <micro-app>內提供了js沙箱方法(v1.0以前跟qiankun沙箱一樣),<micro-app-head>掛載到<micro-app>后,內部會逐一對<micro-app-head>內的script標簽的js綁定作用域,實現js隔離。

          css隔離方案

          默認使用正則將CSS字符串切割成最小單元,每個單元包含一段CSS信息,將所有的信息整理生成CSSTree,遍歷CSSTree的每個規則,添加前綴實現樣式隔離。

          js隔離方案

          micro-app有兩種方式實現js隔離,默認是跟qiankun一樣采用proxy沙箱的方式隔離, 在v1.0發布后支持了基于原生iframe的隔離方式。

          Micro App實戰

          1. 主應用入口文件

          import React from 'react';
          import ReactDOM from 'react-dom';
          import './index.css';
          import Router from './router';
          import microApp from '@micro-zoe/micro-app'
          
          microApp.start()
          
          ReactDOM.render(
            <React.StrictMode>
              <Router />
            </React.StrictMode>,
            document.getElementById('root')
          );
          
          

          調用子應用

          export function MyPage () {
            return (
              <div>
                <h1>子應用</h1>
                // name:應用名稱, url:應用地址
                <micro-app name='my-app' url='http://localhost:3000/'></micro-app>
              </div>
            )
          }
          
          

          2.預覽

          框架優缺點

          優點

          • 接入簡單,組件式引入子應用
          • 團隊持續更新維護
          • js隔離、css隔離、路由同步
          • 支持子應用保活, 需要開啟keep-alive模式
          • 支持fiber模式,提升主應用的渲染性能。

          缺點

          • 1.0之前不支持vite,1.0之后支持了
          • 默認css隔離方式,主應用的樣式還是會污染到子應用。
          • 子應用和主應用必須相同的路由模式,要么同時hash模式,要么同時history模式
          • 依賴于CustomElements和Proxy兩個較新的API。Proxy暫時沒有做兼容,所以對于不支持Proxy的瀏覽器無法運行micro-app。

          作者:韓國芳

          來源:微信公眾號:奇舞精選

          出處:https://mp.weixin.qq.com/s/997pVVxdgpOH6ZsDsFAh2g

          并讠果:quangneng.com/5131/

          Swift 是蘋果公司于2014年推出的一種新的編程語言,用于iOS、macOS、watchOS和tvOS應用程序的開發。Swift因其安全性、速度和現代性而受到開發者的喜愛。在這篇文章中,我們將從入門到進階,通過實戰探探iOS APP,來學習Swift編程。

          一、Swift入門

          1. 環境搭建

          首先,我們需要在蘋果官方網站下載并安裝Xcode,Xcode是蘋果公司提供的IDE,用于Swift語言的開發。安裝完成后,打開Xcode,創建一個新的Swift項目,即可開始Swift編程。

          1. 基礎語法

          Swift的基礎語法包括變量、數據類型、運算符、控制結構等。熟悉這些基礎知識是進行Swift編程的前提。

          1. 函數和閉包

          Swift支持函數和閉包的概念。函數是一段具有特定功能的代碼,而閉包則是一種匿名函數。掌握函數和閉包的定義和使用,有助于我們編寫結構清晰、易于維護的代碼。

          1. 類和結構體

          Swift通過類和結構體實現面向對象編程。類和結構體都是用于封裝數據和行為的數據類型,但類支持繼承和多態,而結構體則不支持。了解它們的使用場景和操作方法,可以更好地設計程序。

          1. 枚舉和協議

          枚舉和協議是Swift語言中常用的特性。枚舉用于定義一組具有共同屬性和行為的值,協議則用于定義一組方法或屬性。掌握枚舉和協議,可以更好地組織代碼,提高可維護性。

          二、Swift進階

          1. 錯誤處理

          在Swift中,錯誤處理是一個重要的環節。通過Error協議和do-catch語句,我們可以有效地處理程序運行過程中出現的錯誤。

          1. 擴展和泛型

          Swift支持擴展和泛型編程。擴展用于為現有的類、結構體、枚舉和協議添加新的功能,泛型則用于編寫可復用的、類型安全的代碼。

          1. 多線程編程

          Swift支持多線程編程,通過DispatchQueue和OperationQueue等API實現。掌握多線程編程,可以充分利用計算機資源,提高程序性能。

          三、實戰探探iOS APP

          1. 項目背景

          探探是一款流行的社交應用,用戶可以通過滑動來選擇喜歡或不喜歡的人。在這個項目中,我們將使用Swift語言開發一個簡化版的探探應用。

          1. 功能需求

          我們的簡化版探探應用將具備以下功能:

          (1)用戶注冊和登錄;

          (2)用戶可以上傳頭像和填寫個人信息;

          (3)用戶可以查看其他用戶的資料,并進行喜歡或不喜歡操作;

          (4)用戶可以查看喜歡自己的人,并進行配對操作。

          1. 技術實現

          在這個項目中,我們將使用以下技術:

          (1)使用UIKit構建用戶界面;

          (2)使用Core Data進行數據存儲;

          (3)使用 Alamofire 進行網絡請求;

          (4)使用 SwiftSoup 解析 HTML 數據。

          1. 開發實踐

          在開發過程中,我們遵循Swift編程規范,采用面向對象編程、錯誤處理、多線程編程等技術,保證代碼質量。同時,我們編寫了詳細的單元測試和文檔,確保應用的穩定性和可維護性。

          1. 性能優化

          為了保證應用的高性能,我們采用了以下優化措施:

          (1)使用懶加載和緩存技術,減少不必要的資源消耗;

          (2)使用UITableView進行列表展示,提高列表的滑動性能;

          (3)使用GCD進行圖片異步加載,提高圖片加載速度。

          1. 項目部署

          在項目部署階段,我們使用Xcode進行應用的打包和發布。同時,我們使用TestFlight進行應用的測試和分發。

          四、Swift高級特性

          1. 內存管理

          Swift使用自動引用計數(ARC)機制來管理內存,但開發者仍需理解內存管理的概念,以避免內存泄漏和野指針問題。了解Swift中的強引用、弱引用、無主引用等概念對于編寫高效和安全的代碼至關重要。

          1. 協議擴展

          Swift的協議擴展允許開發者在不修改原有類的情況下,為協議提供默認實現。這一特性極大地增強了代碼的復用性和靈活性。

          1. 泛型編程

          泛型是Swift中的一個強大特性,它允許開發者編寫適用于任何類型的代碼。泛型函數和泛型類型能夠提高代碼的通用性和類型安全。

          1. 動態性

          Swift雖然是一門靜態語言,但它也提供了一定程度的動態性。例如,使用dynamic關鍵字可以使類成員動態派發,從而支持運行時檢查和修改。

          五、iOS設計模式與實踐

          1. MVC模式

          Model-View-Controller(MVC)是iOS開發中最常用的設計模式。了解MVC模式的原則,能夠幫助開發者更好地組織代碼,實現視圖、模型和控制器之間的解耦。

          1. MVVM模式

          Model-View-ViewModel(MVVM)是MVC模式的一種變體,它通過引入ViewModel層來進一步解耦視圖和模型,提高代碼的可測試性和可維護性。

          1. 單例模式

          單例模式確保一個類只有一個實例,并提供一個全局訪問點。在iOS開發中,單例模式常用于管理共享資源或提供全局服務。

          1. 依賴注入

          依賴注入是一種設計模式,用于實現控制反轉。在Swift中,依賴注入可以幫助我們減少組件之間的耦合,提高代碼的可測試性和可維護性。

          六、iOS應用測試與優化

          1. 單元測試

          單元測試是確保代碼質量的關鍵。在Swift中,使用XCTest框架進行單元測試,可以驗證代碼的各個部分是否按預期工作。

          1. UI測試

          UI測試用于驗證應用的界面和用戶交互是否符合預期。使用XCTest框架進行UI測試,可以確保應用的用戶體驗質量。

          1. 性能優化

          性能優化是iOS應用開發的重要環節。使用Instruments等工具進行性能分析,可以幫助開發者發現并解決性能瓶頸。

          1. 代碼審查

          代碼審查是提高代碼質量的有效手段。通過代碼審查,可以及時發現和修復代碼中的問題,提高代碼的可讀性和可維護性。

          七、項目總結與展望

          通過實戰探探iOS APP的開發,我們不僅學習了Swift編程語言的基礎和進階知識,還了解了iOS應用開發的最佳實踐和設計模式。項目實踐中,我們遇到了各種挑戰,如網絡請求的異步處理、用戶界面的流暢度優化、數據存儲的可靠性等,通過不斷學習和嘗試,我們逐一解決了這些問題,最終完成了一個功能完善、性能良好的社交應用。

          展望未來,隨著Swift語言的不斷發展和iOS平臺的新技術涌現,iOS應用開發將變得更加高效和強大。作為開發者,我們需要不斷學習新技術,提高自己的技能水平,以適應不斷變化的技術環境。Swift和iOS開發將繼續為我們提供廣闊的發展空間和無限的創新可能。


          、CSS初認識


          1、趣調查


          2、“裝修小能手”

          前面我們研究了HTML,回顧下它是做什么的?

          當我們用HTML搭建好網頁的基本骨架,下面請出我們的“裝修小能手”--CSS。

          3、如何學習CSS?

          Python大星前去探探路...

          4、學習必備

          ● 充分利用谷歌瀏覽器Chrome審查元素功能

          CSS權威網站

          https://developer.mozilla.org/zh-CN/docs/Web/CSS/Reference


          二、CSS的基本使用

          1、基本問題

          ● CSS代碼寫在什么地方?

          ● CSS的語法規則?

          2、CSS代碼的書寫位置

          ● 內部樣式表

          書寫在style元素中,一般放在<head></head>中。

          有人可能會問,能放到其他元素里嗎?

          答案:可以。但如果你使用內部樣式表,建議放到head元素中,利于瀏覽器的加載渲染

          >> 舉個栗子:


          ● 內聯樣式表(元素樣式表)

          直接書寫在元素的全局屬性style中


          ● 外部樣式表

          將樣式書寫到獨立的css文件中。

          【1】理由有三:

          ① 解決多頁面樣式重復的問題;

          ② 有利于瀏覽器緩存,提高頁面響應速度;

          ③ 有利于代碼分離,易閱讀和維護。

          【2】如何使用外部樣式表:


          3、CSS代碼的語法

          CSS語法=選擇器 + 聲明塊

          ● 選擇器(Selector

          CSS 選擇器是CSS規則的一部分,使你能應用樣式到指定元素。

          ① 基礎選擇器

          • 標簽選擇器 elementname

          • 類選擇器 .classname

          • ID 選擇器 #idname

          • 屬性選擇器 [attr=value]

          ① 關系選擇器

          • 鄰近兄弟元素選擇器 A + B

          選擇緊跟A元素后的B元素,用+表示,選擇相鄰的第一個兄弟元素。


          • 兄弟元素選擇器 A ~ B

          選擇A元素之后的所有兄弟元素B,作用于多個元素,用~隔開

          • 直接子元素選擇器 A > B

          選擇所有作為A元素的直接子元素B,對更深一層的元素不起作用,用>表示

          • 后代元素選擇器 A B

          選擇所有被A元素包含的B元素,中間用空格隔開,在CSS使用頻率高


          ③ 偽類選擇器

          選中某些元素的某種狀態

          1)link: 超鏈接未訪問時的狀態
          2)visited: 超鏈接訪問過后的狀態
          3)hover: 鼠標懸停狀態
          4)active:激活狀態,鼠標按下狀態
          愛恨法則:love hate
          

          ● 聲明塊

          出現在大括號{}中

          聲明塊中包含很多聲明(屬性),每一個聲明(屬性)表達了某一方面的樣式。


          主站蜘蛛池模板: 无码人妻少妇色欲AV一区二区| 无码一区18禁3D| 精品国产乱子伦一区二区三区| 久久久不卡国产精品一区二区| 丰满人妻一区二区三区视频53| 国产一区二区三区在线影院| 国产乱码一区二区三区爽爽爽| 无码人妻少妇色欲AV一区二区 | 国产成人一区二区三区| 国产成人一区二区精品非洲 | 日韩精品一区二区三区不卡| 久久精品无码一区二区app| 人妻激情偷乱视频一区二区三区| 乱码人妻一区二区三区| 中日韩一区二区三区| 麻豆果冻传媒2021精品传媒一区下载| 亚洲.国产.欧美一区二区三区| 精品3d动漫视频一区在线观看| 中文字幕人妻无码一区二区三区| 亚洲综合一区二区精品导航| 国产精品区一区二区三| 精品日产一区二区三区手机| 人妻视频一区二区三区免费| 亚洲爽爽一区二区三区| 国产一区二区成人| 久久久99精品一区二区| 亚洲色精品VR一区区三区 | 亚洲一区在线视频观看| 午夜在线视频一区二区三区| 久久se精品一区二区影院| 美女视频一区三区网站在线观看| chinese国产一区二区| 亚洲av无码成人影院一区| 国产成人高清视频一区二区| 亲子乱av一区区三区40岁| 国产精品视频第一区二区三区| 国产精品无码一区二区在线观| 国产综合一区二区| 无码AV一区二区三区无码| 亚洲AV无码一区二区三区电影 | 亚洲视频在线观看一区|