实战微前端,从使用qiankun到自写微前端

我本地写的文档,大部分内容我直截了当的截图了。

image.png

image.png

image.png

image.png

image.png

image.png

我根据报错提示进行了调整:

import React from 'react';
import ReactDOM from 'react-dom/client';
import { isInIcestark, setLibraryName } from '@ice/stark-app';

import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

let w_root;
export function mount(props) {
  w_root = ReactDOM.createRoot(props.container);
  w_root.render();
}
export function unmount(props) {
  w_root.unmount();
}
setLibraryName('microApp');

const root = ReactDOM.createRoot(document.getElementById('root'));
if (!isInIcestark()) {
  root.render(
    
      
    
  );
}

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
import { useEffect } from 'react';

import './App.css';

function App() {
  useEffect(() => {
    console.log('useEffect');
    return () => {
      console.log('unmout');
    }
  })
  return (
    
我是create-react-app的独立项目
); } export default App;

子应用的生命周期感知:顺利在进入的时候打印到“useEffect”, 离开的时候打印了"unmout", 完成将普通的create-react-app 项目成为子应用。

性能优化 性能优化 | icestark
image.png

image.png

image.png
第二步:主应用接入
  1. yarn add qiankun

  2. index.js 文件 (配置路由)

    import React from 'react';
    import ReactDOM from 'react-dom/client';
    import { BrowserRouter } from "react-router-dom";
    
    import './index.css';
    import App from './App';
    import reportWebVitals from './reportWebVitals';
    
    const root = ReactDOM.createRoot(document.getElementById('root_main'));
    root.render(
      
        
          
        
      
    );
    
    // If you want to start measuring performance in your app, pass a function
    // to log results (for example: reportWebVitals(console.log))
    // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
    reportWebVitals();
    
    
  1. 主应用App.js文件(入口文件):

    import { registerMicroApps, start } from 'qiankun';
    import { Routes, Route, Link } from "react-router-dom";
    import { useEffect } from 'react';
    
    import './App.css';
    
    
    function App() {
      useEffect(() => {
        registerMicroApps([
          {
            name: 'child', // app name registered
            entry: '//localhost:7001',
            container: '#Child',
            activeRule: 'child'
          }, {
            name: 'about', // app name registered
            entry: '//localhost:7001',
            container: '#about',
            activeRule: 'about'
          },
        ]);
        start();
      }, [])
      return (
        
    headers
    main

    child

    about

    /
    enter
    } /> main
    } />
    } />
} />
); } export default App; App.css 样式文件
.App {
  width: 100vw;
  height: 100vh;
  overflow: hidden;
}
  • 上一步中,只有1,3是跟逻辑相关的,2. 4都是强调了第一步的改动,到此主应用就完成了。

  • 第三步:子应用接入
    1. yarn eject

    2. 修改配置:webpack.config.js文件


      image.png

      image.png
    import React from 'react';
    import ReactDOM from 'react-dom/client';
    import { BrowserRouter } from "react-router-dom";
    
    import './index.css';
    import App from './App';
    import reportWebVitals from './reportWebVitals';
    
    
    /**
     * bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
     * 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
     */
    export async function bootstrap() {
      console.log('react app bootstraped');
    }
    
    /**
     * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
     */
    let root_child
    export async function mount(props) {
      root_child = ReactDOM.createRoot(props.container ? props.container.querySelector('#root') : document.getElementById('root'));
      root_child.render(
        
          
            
          
        
      );
    }
    
    /**
     * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
     */
    export async function unmount(props) {
      console.log(props.container.querySelector('#root'))
        // unmountComponentAtNode会报错,注释掉后不影响使用
      // ReactDOM.unmountComponentAtNode(
      //   props.container ? props.container.querySelector('#root') : document.getElementById('root'),
      // );
    }
    
    /**
     * 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
     */
    export async function update(props) {
      console.log('update props', props);
    }
    
    if (!window.__POWERED_BY_QIANKUN__) {
      // 不是qiankun过来的就正常展示
      const root = ReactDOM.createRoot(document.getElementById('root'));
      root.render(
        
          
            
          
        
      );
    }
    
    // If you want to start measuring performance in your app, pass a function
    // to log results (for example: reportWebVitals(console.log))
    // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
    reportWebVitals();
    
    • 这里重点注意:官网给的是

      export async function mount(props) {
        ReactDOM.render(, props.container ? props.container.querySelector('#root') : document.getElementById('root'));
      }
      

    这个是旧版本的写法,新版本的react需要ReactDOM.createRoo,然后render来作为渲染方式,不然会报错。


    image.png

    image.png

    image.png

    image.png

    image.png

    image.png

    这里我只是简单实现了在主应用中插入子应用,效果如上图。

    思路
    1. 请求子应用url地址,得到入口js文件,这里也需要配置跨域哦
    2. 子应用向window注册wei_mount事件(我目前没有想到其他更好的办法)
    3. 主应用修改window._IS_WS_WEI,表示当前为微服务
    4. 主应用写上根模块id="child"
    主应用实现代码

    这里注意bundle.js的入口路径不同项目也许有差别,可以做个微调。

    app.js文件

    import { useState } from 'react';
    import { useEffect } from 'react';
    import './App.css';
    
    window._IS_WS_WEI_ = 'wangshi的demo';
    let a;
    const runScript = async (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);
      });
    };
    
    function App() {
      useEffect(() => {
        asyncCall();
      }, [])
      async function asyncCall() {
        if (a) {
          return;
        }
        a = true;
        await runScript('http://localhost:3001/static/js/bundle.js')
        window.wei_mount({ container: document.getElementById('child') })
      }
      return (
        
    我是主
    ); } export default App;

    入口文件路径查找: 我是这样确认我的入口位置的


    image.png

    image.png
    image.png

    Why Not Iframe

    为什么不用 iframe,这几乎是所有微前端方案第一个会被 challenge 的问题。但是大部分微前端方案又不约而同放弃了 iframe 方案,自然是有原因的,并不是为了 "炫技" 或者刻意追求 "特立独行"。

    如果不考虑体验问题,iframe 几乎是最完美的微前端解决方案了。

    iframe 最大的特性就是提供了浏览器原生的硬隔离方案,不论是样式隔离、js 隔离这类问题统统都能被完美解决。但他的最大问题也在于他的隔离性无法被突破,导致应用间上下文无法被共享,随之带来的开发体验、产品体验的问题。

    1url 不同步。浏览器刷新 iframe url 状态丢失、后退前进按钮无法使用。
    2UI 不同步,DOM 结构不共享。想象一下屏幕右下角 1/4 的 iframe 里来一个带遮罩层的弹框,同时我们要求这个弹框要浏览器居中显示,还要浏览器 resize 时自动居中..
    3全局上下文完全隔离,内存变量不共享。iframe 内外系统的通信、数据同步等需求,主应用的 cookie 要透传到根域名都不同的子应用中实现免登效果。
    4慢。每次子应用进入都是一次浏览器上下文重建、资源重新加载的过程。

    其中有的问题比较好解决(问题1),有的问题我们可以睁一只眼闭一只眼(问题4),但有的问题我们则很难解决(问题3)甚至无法解决(问题2),而这些无法解决的问题恰恰又会给产品带来非常严重的体验问题, 最终导致我们舍弃了 iframe 方案。

    -----摘抄于qiankun

    你可能感兴趣的:(实战微前端,从使用qiankun到自写微前端)