qiankun保姆级教程(React)

1 篇文章 0 订阅
1 篇文章 0 订阅

一、介绍

1、什么是qiankun

qiankun 是一个基于 single-spa 的微前端实现库,旨在帮助大家能更简单、无痛的构建一个生产可用微前端架构系统。(官方说的)

正所谓乾坤无偏奉一体,万象森罗尽在其中。在我看来qiankun就是一个乾坤百宝袋(主应用),可以把很多应用(微应用)都装进来,每个应用都可以独立开发,独立部署,技术栈自主。有了qiankun企业的业务就可以实现真正的增量升级,将一个业务量庞大的系统拆分成多个自主独立的模块,每个微应用独立运行解决单体应用在一个相对长的时间跨度下,由于参与的人员、团队的增多、变迁,从一个普通应用演变成一个巨石应用后,随之而来的应用不可维护的问题。(再也不用担心程序猿们在屎山修改业务)

2、qiankun的特性

技术栈无关:微应用可以使用任何技术栈,只需和主应用连接皆可实现开箱即用。

独立开发,独立部署:统一系统的不同应用可由不同团队独立开发,大大节省项目开发周期。

增量升级:在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略。

独立运行:每个微应用之间状态隔离,运行时状态不共享。

资源预加载:在浏览器空闲时间预加载未打开的微应用资源,加速微应用打开速度。

3、qiankun vs iframe

为什么不使用iframe这种原生的更为简便的方案而要另辟蹊径的使用qiankun呢?

原因在于iframe最大的的问题在于他的隔离性太强了,很难对其进行突破,导致应用上下文很难被共享,这带来了很多开发体验和产品体验的问题。这也是为什么不用iframe而去选择使用qiankun的原因。

二、qiankun搭建

1、安装qiankun

主应用和微应用都要进行安装哟~

$ yarn add qiankun # 或者 npm i qiankun -S

2、通用配置

在所有应用中安装插件 react-app-rewired

npm install react-app-rewired --save-dev 或 yarn add react-app-rewired --dev

在package.json文件中修改启动脚本

"scripts": {
    "start": "react-app-rewired start",
  },

 在根目录新增文件config-overrides.js

const { name } = require('./package');

module.exports = {
  webpack: (config) => {
    //将微应用打包成 umd 模块时,设置输出的全局变量名。
    config.output.library = `${name}-[name]`; 
    //设置打包输出的模块格式为 umd。
    config.output.libraryTarget = 'umd';
    // webpack 5 需要把 jsonpFunction 替换成 chunkLoadingGlobal
    // config.output.jsonpFunction = `webpackJsonp_${name}`; 
    // 指定在浏览器环境中使用全局对象
    config.output.globalObject = 'window';

    return config;
  },

  devServer: (_) => {
    const config = _;
//为开发服务器的响应头添加 Access-Control-Allow-Origin,允许跨域请求(注意仅适用于开发环境)。
    config.headers = {
      'Access-Control-Allow-Origin': '*',
    };
    // 当路由请求404时,返回根目录index.html,常用于单页应用的路由配置。
    config.historyApiFallback = true;
    //禁用热模块替换(HMR),即禁止在开发服务器中热更新模块。
    config.hot = false;
    //禁用开发服务器的内容监视功能,这样开发服务器不会监听文件的变化。
    config.watchContentBase = false;
    //禁用开发服务器的实时重新加载功能。
    config.liveReload = false;

    return config;
  },
};

qiankun 在微应用中使用 react-app-rewired 是为了能够定制 Create React App 的 webpack 配置,以适应微应用的需求。这样可以确保微应用在 qiankun 的环境中能够正常运行并集成到宿主应用中。

3、在主应用中注册微应用

/*主应用的index.tsx(不同项目架构有不同的注册方法,可以在任何地方注册子应用。
一般来说,我们会在主应用的入口文件或根组件中注册子应用。)*/
import { registerMicroApps, start } from 'qiankun';
const micors = [
  {
    name: 'react app', // 微应用的名字,自拟最好能一眼看出这个应用的功能
    entry: '//localhost:7100', // 微应用的入口地址
    container: '#container', // 微应用要挂载的容器id
    activeRule: '/app1',// 微应用对应的路由匹配规则
  },
  {
    name: 'vue app',
    entry: { scripts: ['//localhost:7100/main.js'] },
    container: '#container2',
    activeRule: '/app2',
  },
];
registerMicroApps(micors,{
    beforeLoad: (app) => {
        return Promise.resolve();
    },
    beforeMount: (app) => {
        return Promise.resolve();
    },
});

start();// 最后别忘了启动哟

当微应用信息注册完之后,一旦浏览器的 url 发生变化,便会自动触发 qiankun 的匹配逻辑,所有 activeRule 规则匹配上的微应用就会被插入到指定的 container 中,同时依次调用微应用暴露出的生命周期钩子。

如果微应用不是直接跟路由关联的时候,你也可以选择手动加载微应用的方式如下:

import { loadMicroApp } from 'qiankun';

loadMicroApp({
  name: 'app',
  entry: '//localhost:7100',
  container: '#yourContainer',
});

 配置微应用挂载点(这个挂载点名字要和上面的container对应)

// App.tsx
import { Route, BrowserRouter, Routes, Link } from "react-router-dom";
import "./App.css";

function App() {
  return (
    <BrowserRouter>
      <div className="App">
        <Link to="/app1">app1</Link>
        <Link to="/app2">app2</Link>
        <div id="container"></div>
        <Routes>
          <Route path="/app1">app1</Route>
          <Route path="/app2">app2</Route>
        </Routes>
      </div>
    </BrowserRouter>
  );
}

export default App;

 以上是我主应用实现路由跳转的一些方式。

4、配置微应用

在所有微应用src 目录新增 public-path.js文件

if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

这个文件是用于根据qiankun注入的全局变量来动态配置webpack的publicPath属性,以确保子应用的静态资源能够正确加载。(指定静态资源路径的,不然图片那些静态资源就加载不出来。)

修改微应用入口文件index.tsx

// index.tsx
import "./public-path.js";//导入上一步配置的文件,用于正确加载静态资源文件
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { BrowserRouter } from "react-router-dom";

export async function bootstrap() {
  console.log("[react16] react app bootstraped");
}

export async function mount(props: any) {
  console.log("[react16] props from main framework", props);
  ReactDOM.render(
    <BrowserRouter
      basename={window.__POWERED_BY_QIANKUN__ ? "app1" : undefined}
    >
      <App />
    </BrowserRouter>,
    props.container
      ? props.container.querySelector("#root")
      : document.getElementById("root")
  );
}

export async function unmount(props: any) {
  const { container } = props;
  ReactDOM.unmountComponentAtNode(
    container
      ? container.querySelector("#root")
      : document.querySelector("#root")
  );
}
// @ts-ignore
if (!window.__POWERED_BY_QIANKUN__) {
  ReactDOM.render(
    <BrowserRouter>
      <App />
    </BrowserRouter>,
    document.getElementById("root")
  );
}
reportWebVitals();

三、主应用与子应用间的通信

在完成以上步骤之后你就能得到一个由微前端搭建起来的应用啦,当然如果仅仅只是这样这还远远不够,我们还需要实现主应用和子应用的通信。

1、qiankun自带的全局状态API

首先在主应用的入口文件index.tsx写入以下代码

import { initGlobalState, MicroAppStateActions } from "qiankun";

const state = {
  nickname: "crazy",
};
// 初始化 state
const actions: MicroAppStateActions = initGlobalState(state);

actions.onGlobalStateChange((state, prev) => {
  // state: 变更后的状态; prev 变更前的状态
  console.log("base", state, prev);
});

在注册微应用时将props注册进微应用

registerMicroApps([
  {
    name: "app1",
    entry: "//localhost:3011",
    container: "#container",
    activeRule: "/app1",
    props: { state },
  },
  {
    name: "app2",
    entry: "//localhost:3012",
    container: "#container",
    activeRule: "/app2",
    props: { state },
  },
]);

在主应用的入口文件中可以使用actions.setGlobalState()对全局变量进行修改

actions.setGlobalState({ ...state, nickname: "123", age: 20 });

在微应用中可以使用props.setGlobalState()对已有的state对象中的属性进行修改,但是没有权限向state对象中进行属性的添加,如果你使用props.setGlobalState({ ...state, nickname: "123" ,age:19});那么nickname中的数据会被更改,但是age这一项会被忽略无法添加到全局变量中,而主应用则可以正常添加。

props.setGlobalState({ ...state, nickname: "123" });

1.1局限性

如上所说qiankun自带的通信方式,子应用对全局状态中的属性只有修改的权限,但是无法向全局状态中新增或者删除属性。

2、localStorage通信

使用localStorage进行主应用与微应用间的通信是目前我觉得最直接的通信方式,不管是主应用还是微应用,使用以下两行代码都可以很快的进行数据的传递,但是不能滥用localStorage,我们将各应用公用的数据放到localStorage就足够了,否则会出现影响性能,增加跨站点脚本攻击风险等一系列的问题。

localStorage.setItem("username", "crazy");
localStorage.getItem("username");

四、API

1、registerMicroApps(apps, lifeCycles?)

用于注册子应用

  • 参数

    • apps - Array<RegistrableApp> - 必选,微应用的一些注册信息
    • lifeCycles - LifeCycles - 可选,全局的微应用生命周期钩子
  • 类型

    • RegistrableApp

      • name - string - 必选,微应用的名称,微应用之间必须确保唯一。

      • entry - string | { scripts?: string[]; styles?: string[]; html?: string } - 必选,微应用的入口。

        • 配置为字符串时,表示微应用的访问地址,例如 https://qiankun.umijs.org/guide/
        • 配置为对象时,html 的值是微应用的 html 内容字符串,而不是微应用的访问地址。微应用的 publicPath 将会被设置为 /
      • container - string | HTMLElement - 必选,微应用的容器节点的选择器或者 Element 实例。如container: '#root' 或 container: document.querySelector('#root')

      • activeRule - string | (location: Location) => boolean | Array<string | (location: Location) => boolean> - 必选,微应用的激活规则。

        • 支持直接配置字符串或字符串数组,如 activeRule: '/app1' 或 activeRule: ['/app1', '/app2'],当配置为字符串时会直接跟 url 中的路径部分做前缀匹配,匹配成功表明当前应用会被激活。
        • 支持配置一个 active function 函数或一组 active function。函数会传入当前 location 作为参数,函数返回 true 时表明当前微应用会被激活。如 location => location.pathname.startsWith('/app1')
      • loader - (loading: boolean) => void - 可选,loading 状态发生变化时会调用的方法。

      • props - object - 可选,主应用需要传递给微应用的数据。

    • LifeCycles

      type Lifecycle = (app: RegistrableApp) => Promise<any>;

      • beforeLoad - Lifecycle | Array<Lifecycle> - 可选
      • beforeMount - Lifecycle | Array<Lifecycle> - 可选
      • afterMount - Lifecycle | Array<Lifecycle> - 可选
      • beforeUnmount - Lifecycle | Array<Lifecycle> - 可选
      • afterUnmount - Lifecycle | Array<Lifecycle> - 可选
registerMicroApps([
  {
    name: 'react app', // 微应用的名字,自拟最好能一眼看出这个应用的功能
    entry: '//localhost:7100', // 微应用的入口地址
    container: '#container', // 微应用要挂载的容器id
    activeRule: '/app1',// 微应用对应的路由匹配规则
    loader:(loading)=>{},// loading 状态发生变化时会调用的方法。
    props: {},// 传递给子应用的props
  },
],
{
    // 各生命周期调用的方法
    beforeLoad: async (app) => {},
    beforeMount: async (app) => {},
    afterMount: async (app) => {},
    afterUnmount: async (app) => {},
    beforeUnmount: async (app) => {},
});

2、start(opts?)

用于启动qiankun

  • prefetch - boolean | 'all' | string[] | (( apps: RegistrableApp[] ) => { criticalAppNames: string[]; minorAppsName: string[] }) - 可选,是否开启预加载,默认为 true

    • 配置为 true 则会在第一个微应用 mount 完成后开始预加载其他微应用的静态资源

      配置为 'all' 则主应用 start 后即开始预加载所有微应用静态资源

      配置为 string[] 则会在第一个微应用 mounted 后开始加载数组内的微应用资源

      配置为 function 则可完全自定义应用的资源加载时机 (首屏应用及次屏应用)

    • sandbox - boolean | { strictStyleIsolation?: boolean, experimentalStyleIsolation?: boolean } - 可选,是否开启沙箱,默认为 true

    • singular - boolean | ((app: RegistrableApp<any>) => Promise<boolean>); - 可选,是否为单实例场景,单实例指的是同一时间只会渲染一个微应用。默认为 true

    • fetch - Function - 可选,自定义的 fetch 方法。

    • getPublicPath - (entry: Entry) => string - 可选,参数是微应用的 entry 值。

    • getTemplate - (tpl: string) => string - 可选。

    • excludeAssetFilter - (assetUrl: string) => boolean - 可选,指定部分特殊的动态加载的微应用资源(css/js) 不被 qiankun 劫持处理。

    start();

3、setDefaultMountApp(appLink) 

用于设置主应用启动后默认进入的微应用。

  • 参数

    • appLink - string - 必选
import { setDefaultMountApp } from 'qiankun';

setDefaultMountApp('/app1');

4、runAfterFirstMounted(effect)

用于第一个微应用 mount 后需要调用的方法,比如开启一些监控或者埋点脚本。

  • 参数

    • effect - () => void - 必选
import { runAfterFirstMounted } from 'qiankun';

runAfterFirstMounted(() => startMonitor());

5、loadMicroApp(app, configuration?)

用于手动加载微应用

  • 参数

    • app - LoadableApp - 必选,微应用的基础信息

      • name - string - 必选,微应用的名称,微应用之间必须确保唯一。
      • entry - string | { scripts?: string[]; styles?: string[]; html?: string } - 必选,微应用的入口(详细说明同上)。
      • container - string | HTMLElement - 必选,微应用的容器节点的选择器或者 Element 实例。如container: '#root' 或 container: document.querySelector('#root')
      • props - object - 可选,初始化时需要传递给微应用的数据。
    • configuration - Configuration - 可选,微应用的配置信息

      • sandbox - boolean | { strictStyleIsolation?: boolean, experimentalStyleIsolation?: boolean } - 可选,是否开启沙箱,默认为 true

      • singular - boolean | ((app: RegistrableApp<any>) => Promise<boolean>); - 可选,是否为单实例场景,单实例指的是同一时间只会渲染一个微应用。默认为 false

      • fetch - Function - 可选,自定义的 fetch 方法。

      • getPublicPath - (entry: Entry) => string - 可选,参数是微应用的 entry 值。

      • getTemplate - (tpl: string) => string - 可选

      • excludeAssetFilter - (assetUrl: string) => boolean - 可选,指定部分特殊的动态加载的微应用资源(css/js) 不被 qiankun 劫持处理

  • 返回值 - MicroApp - 微应用实例

    • mount(): Promise<null>;
    • unmount(): Promise<null>;
    • update(customProps: object): Promise<any>;
    • getStatus(): | "NOT_LOADED" | "LOADING_SOURCE_CODE" | "NOT_BOOTSTRAPPED" | "BOOTSTRAPPING" | "NOT_MOUNTED" | "MOUNTING" | "MOUNTED" | "UPDATING" | "UNMOUNTING" | "UNLOADING" | "SKIP_BECAUSE_BROKEN" | "LOAD_ERROR";
    • loadPromise: Promise<null>;
    • bootstrapPromise: Promise<null>;
    • mountPromise: Promise<null>;
    • unmountPromise: Promise<null>;
import { loadMicroApp } from 'qiankun';
import React from 'react';

class App extends React.Component {
  containerRef = React.createRef();
  microApp = null;

  componentDidMount() {
    this.microApp = loadMicroApp({
      name: 'app1',
      entry: '//localhost:1234',
      container: this.containerRef.current,
      props: { brand: 'qiankun' },
    });
  }

  componentWillUnmount() {
    this.microApp.unmount();
  }

  componentDidUpdate() {
    this.microApp.update({ name: 'kuitos' });
  }

  render() {
    return <div ref={this.containerRef}></div>;
  }
}

 6、prefetchApps(apps, importEntryOpts?)

用于手动预加载指定的微应用静态资源。

  • 参数

    • apps - AppMetadata[] - 必选 - 预加载的应用列表
    • importEntryOpts - 可选 - 加载配置
  • 类型

    • AppMetadata
      • name - string - 必选 - 应用名
      • entry - string | { scripts?: string[]; styles?: string[]; html?: string } - 必选,微应用的 entry 地址
import { prefetchApps } from 'qiankun';

prefetchApps([
  { name: 'app1', entry: '//localhost:7001' },
  { name: 'app2', entry: '//localhost:7002' },
]);

7、addGlobalUncaughtErrorHandler(handler)

用于添加全局的未捕获异常处理器。

  • handler - (...args: any[]) => void - 必选
import { addGlobalUncaughtErrorHandler } from 'qiankun';

addGlobalUncaughtErrorHandler((event) => console.log(event));

8、removeGlobalUncaughtErrorHandler(handler)

用于移除全局的未捕获异常处理器。

  • 参数

    • handler - (...args: any[]) => void - 必选
import { removeGlobalUncaughtErrorHandler } from 'qiankun';

removeGlobalUncaughtErrorHandler(handler);

9、initGlobalState(state)

用于定义全局状态,并返回通信方法,建议在主应用使用,微应用通过 props 获取通信方法。

  • 参数

    • state - Record<string, any> - 必选
  • 返回

    • MicroAppStateActions

      • onGlobalStateChange: (callback: OnGlobalStateChangeCallback, fireImmediately?: boolean) => void, 在当前应用监听全局状态,有变更触发 callback,fireImmediately = true 立即触发 callback

      • setGlobalState: (state: Record<string, any>) => boolean, 按一级属性设置全局状态,微应用中只能修改已存在的一级属性

      • offGlobalStateChange: () => boolean,移除当前应用的状态监听,微应用 umount 时会默认调用

 主应用

import { initGlobalState, MicroAppStateActions } from 'qiankun';

// 初始化 state
const actions: MicroAppStateActions = initGlobalState(state);

actions.onGlobalStateChange((state, prev) => {
  // state: 变更后的状态; prev 变更前的状态
  console.log(state, prev);
});
actions.setGlobalState(state);
actions.offGlobalStateChange();

 微应用

// 从生命周期 mount 中获取通信方法,使用方式和 master 一致
export function mount(props) {
  props.onGlobalStateChange((state, prev) => {
    // state: 变更后的状态; prev 变更前的状态
    console.log(state, prev);
  });

  props.setGlobalState(state);
}

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
qiankun是一个由蚂蚁金服开源的微前端解决方案,旨在解决多个团队或多个子系统之间协同开发的问题,同时提高前端应用的可维护性和可扩展性。 微前端是指将一整个网站或应用分为若干个小的部分,每个部分都是一个独立的子应用,可以由不同的团队或开发者进行独立开发和管理,并最终将这些子应用集成起来形成一个完整的网站或应用。微前端可以提高应用的可伸缩性、可维护性和灵活性,同时为团队之间协同开发提供了更好的支持。 qiankun通过使用Vue、React等主流框架来构建子应用,并通过一个主应用来动态加载和协调这些子应用,有效解决了子应用的模块化开发、独立部署和协同开发的问题。此外,qiankun还提供了完备的生命周期钩子函数和通信机制,方便子应用与主应用之间进行数据交互和状态同步。 除此之外,qiankun还支持多种场景下的子应用加载方式,包括iframe、HTTP、和WebComponents等,灵活地满足了不同场景下的应用需求。同时,qiankun还提供了插件化的开发模式,可以集成多种生态工具进行开发,如React Devtools、Vue Devtools、Webpack等。 综上所述,qiankun是一款非常优秀的微前端解决方案,不仅提升了前端的可维护性和可扩展性,同时为团队协同开发提供了更好的支持,对于企业应用开发来说具有非常重要的意义。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值