3

从零开始,一步步学习微前端

 1 year ago
source link: https://www.fly63.com/article/detial/12457
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

更新日期: 2023-04-28阅读: 50标签: 微前端分享

扫一扫分享

在实习的这段时间接触了很多新东西,比如微前端就是其中之一,在这里小浪就来聊聊微前端中的qiankun框架

1.1 为什么需要微前端

当我们开发大型的前端应用时,通常需要将应用拆分成多个子应用进行开发和维护。这时候微前端就显得尤为重要。微前端是一种架构模式,它将前端应用程序拆分成多个更小的、相对独立的部分,每个部分都可以独立开发、测试、部署和扩展。在微前端架构中,每个子应用都是一个独立的应用程序,可以独立部署和运行。这样,我们就可以将前端应用程序的开发、测试、部署和维护工作分解成多个更小的任务,提高应用程序的可维护性和可扩展性。微前端的实现方式有多种,其中比较常见的方式是通过 iframe、Web Components 或者 JavaScript 模块加载器来实现子应用的隔离和独立运行。通过这些方式,不同的子应用之间可以互相独立、互相通信,从而实现一个完整的前端应用程序。

1.2 如何判断自己的项目需要使用微前端

  1. 项目功能逐渐增多,代码规模庞大,导致代码维护和开发效率低下;
  2. 项目需要集成多个不同技术栈的模块或服务;
  3. 团队成员分散,各自负责开发不同的模块或服务,需要实现独立开发和部署;
  4. 项目需要支持独立的生命周期管理和版本控制;
  5. 需要实现高可用性和弹性伸缩;
  6. 需要实现动态加载和卸载子应用等场景。
所以呢,小浪觉得如果项目具有上面这些特点,那么可以考虑使用微前端来优化项目架构和提升开发效率。

1.3 主应用和子应用

qiankun 官网地址是:qiankun.umijs.org/
官网的例子和教程都很详细,更新速度也快,大家想具体使用的可以去官网查阅

qiankun 是一个基于 Single-SPA 的微前端解决方案,它提供了一种轻量级的前端架构,可以将多个子应用整合成一个整体应用。qiankun 的架构包含两个主要的角色:

  • 主应用(Master Application):负责整个应用的框架搭建、路由分发和子应用的注册和协调管理。
  • 子应用(Micro Application):独立开发的前端应用程序,可以独立运行,也可以作为主应用的一个子模块运行。

1.4 qiankun的特点

qiankun 的主要特点包括:

  • 完备的生命周期管理:qiankun 提供了完备的子应用生命周期管理方案,可以自动加载、启动、挂载、卸载和卸载子应用。
  • 灵活的应用路由分发:qiankun 提供了灵活的路由分发方案,可以自定义路由匹配规则,可以通过主应用的路由来匹配子应用的路由。
  • 独立的应用状态管理:qiankun 提供了独立的状态管理方案,每个子应用都可以独立管理自己的状态,主应用可以通过 props 方式传递数据给子应用,也可以通过事件总线的方式进行通信。

1.5 如何使用微前端

在使用 qiankun 构建微前端应用时,我们需要按照以下步骤进行:

  1. 创建主应用:主应用负责整个应用的框架搭建、路由分发和子应用的注册和协调管理。可以使用任何前端框架进行开发,例如 reactvueangular 等。
  2. 创建子应用:子应用是独立开发的前端应用程序,可以独立运行,也可以作为主应用的一个子模块运行。每个子应用都可以使用任何前端框架进行开发,例如 React、Vue、Angular 等。
  3. 注册子应用:主应用需要在启动时注册所有的子应用。注册时需要指定子应用的名称、访问地址、加载方式和路由配置等信息。
  4. 启动子应用:主应用在启动时会自动加载和启动所有的子应用,每个子应用会根据配置的访问地址自动加载。
  5. 协调子应用:主应用需要协调管理所有的子应用,包括子应用的加载、启动、挂载、卸载和卸载等操作。主应用可以通过 props 方式传递数据给子应用,也可以通过事件总线的方式进行通信。
  6. 卸载子应用:当子应用不再需要时,主应用可以卸载子应用并释放资源,以提高系统的性能和稳定性。

除此之外,qiankun 还提供了多种高级特性,例如异步加载、公共依赖、样式隔离、缓存优化等,可以进一步提高应用程序的性能和可维护性。

2.使用qiankun搭建例子:

这里举一个demo例子,大家如果感兴趣可以按照小浪这样来进行搭建 一个简单的 qiankun 实例demo

  • 主应用使用 React
  • 子应用1 使用 Vue 3
  • 子应用2 使用 React

2.1创建主应用

首先需要创建主应用,我们使用 create-react-app 快速创建一个 React 应用:

npx create-react-app qiankun-example
cd qiankun-example
npm install qiankun

修改主应用入口文件 src/index.js,加入以下代码:

import React from 'react';
import Reactdom from 'react-dom';
import { registerMicroApps, start } from 'qiankun';

// 定义子应用列表
const apps = [
  {
    name: 'vue3',
    entry: '//localhost:8081',
    container: '#subapp-viewport',
    activeRule: '/vue3',
  },
  {
    name: 'react-app',
    entry: '//localhost:8082',
    container: '#subapp-viewport',
    activeRule: '/react-app',
  },
];

// 注册子应用
registerMicroApps(apps);

// 启动 qiankun
start();

在上面小浪首先定义了两个子应用,分别是 Vue 3 应用和 React 应用。然后通过 registerMicroApps 函数注册子应用,启动 qiankun。

2.2 创建子应用1:Vue 3

在主应用中我们定义了一个名为 vue3 的子应用,需要先创建子应用的代码。在 vue-cli 中创建一个 Vue 3 应用(大家也可以用vite搭建,这里我用vue-cli):

vue create subapp-vue3

安装依赖:

cd subapp-vue3
npm install

修改 src/main.js,加入以下代码:

import { createApp } from 'vue';
import App from './App.vue';

let instance = null;

function render() {
  instance = createApp(App).mount('#app');
}

// 开发环境下直接渲染
if (process.env.NODE_ENV === 'development') {
  render();
}

// 导出渲染函数,qiankun 会调用此函数渲染子应用
export async function bootstrap() {}
export async function mount() {
  render();
}
export async function unmount() {
  instance.$destroy();
}

上面小浪定义了子应用的渲染函数,用于在主应用中渲染子应用。在开发环境下直接渲染,而在生产环境下则通过导出 bootstrap、mount 和 unmount 函数,由 qiankun 调用。

2.3 创建子应用2:React 应用

我们需要先创建 React 应用:

npx create-react-app subapp-react

安装依赖:

cd subapp-react
npm install

修改 src/index.js,加入以下代码:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

let instance = null;

function render() {
  ReactDOM.render(<App />, document.getElementById('root'));
}

// 开发环境下直接渲染
if (process.env.NODE_ENV === 'development') {
  render();
}

// 导出渲染函数,qiankun 会调用此函数渲染子应用
export async function bootstrap() {}
export async function mount() {
  render();
}
export async function unmount() {
  instance.$destroy();
}

这里同样定义了子应用的渲染函数,用于在主应用中渲染子应用。在开发环境下直接渲染,而在生产环境下则通过导出 bootstrap、mount 和 unmount 函数,由 qiankun 调用。

2.4 启动应用

我们需要分别启动主应用和两个子应用

# 启动主应用
cd qiankun-example
npm start

# 启动 Vue 3 子应用
cd subapp-vue3
npm run serve -- --port 8081

# 启动 React 子应用
cd subapp-react
npm start -- --port 8082

然后在浏览器中访问 **http://localhost:3000**,即可看到主应用的页面。在主应用中点击“Vue 3”或“React App”按钮,即可跳转到对应的子应用页面。在子应用页面中修改内容,可以看到主应用中的内容也随之变化。

子应用中需要引入 qiankun:

注意:在子应用中需要引入 qiankun 库,在 Vue 3 应用中需要安装 qiankun,如下:

npm install qiankun
// Vue 3 子应用中需要引入 qiankun
import { createApp } from 'vue';
import { registerMicroApps, start } from 'qiankun';
import App from './App.vue';

createApp(App).mount('#app');

// 注册子应用
registerMicroApps([...]);

// 启动 qiankun
start();

React子应用

// React 子应用中需要引入 qiankun
import React from 'react';
import ReactDOM from 'react-dom';
import { registerMicroApps, start } from 'qiankun';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));

// 注册子应用
registerMicroApps([...]);

// 启动 qiankun
start();

registerMicroApps([...]) 中的 [...] 是一个数组,其中包含了注册的子应用的配置信息,每个配置项是一个对象,至少包含以下几个属性:

  • name:子应用的名称,需要保证唯一性。
  • entry:子应用的入口 URL。
  • container:子应用渲染的容器,通常是一个 DOM 元素或一个选择器。
  • activeRule:子应用的激活规则,指定该子应用应该在哪些 URL 下被激活。
registerMicroApps([
  {
    name: 'vue3',
    entry: '//localhost:8081',
    container: '#vue3',
    activeRule: '/vue3',
  },
  {
    name: 'react',
    entry: '//localhost:8082',
    container: '#react',
    activeRule: '/react',
  },
]);

以上代码注册了两个子应用 一个是 Vue 3 应用,一个是 React 应用。

  1. name 分别为 'vue3' 和 'react',
  2. entry 分别为 '//localhost:8081' 和 '//localhost:8082',
  3. container 分别为 '#vue3' 和 '#react',
  4. activeRule 分别为 '/vue3' 和 '/react'。

1.entry 属性的值应该是子应用的访问 URL,可以是本地开发服务器的地址,也可以是部署到服务器上的地址。container 属性的值可以是一个 DOM 元素,也可以是一个选择器字符串。在子应用中,渲染组件时需要将组件挂载到该容器中。activeRule 属性的值是一个路径匹配规则,表示子应用应该在哪些 URL 下被激活,可以是字符串或正则表达式。如果当前 URL 匹配了该规则,则该子应用会被激活。

2.子应用中也需要写 registerMicroApps([...]) 来注册子应用。不同之处在于,子应用注册时需要指定一些配置项,以便于在主应用中正确加载和管理子应用。 在子应用中,通常会将 registerMicroApps([...]) 封装成一个函数,以便于主应用加载子应用时调用该函数注册子应用。例如:

import { registerMicroApps, start } from 'qiankun';

function render() {
  // 渲染子应用
}

function initApp() {
  // 初始化子应用
}

// 注册子应用
function register() {
  registerMicroApps([
    {
      name: 'vue3',
      entry: '//localhost:8081',
      container: '#vue3',
      activeRule: '/vue3',
    },
  ]);

  start();
}

// 启动子应用
function bootstrap() {
  initApp();
  render();
}

// 如果是独立运行,直接启动子应用
if (!window.__POWERED_BY_QIANKUN__) {
  bootstrap();
}

// 如果是作为子应用加载,注册子应用
export async function mount(props) {
  bootstrap();
}

// 如果是作为子应用卸载,销毁子应用
export async function unmount(props) {
  // 销毁子应用
}

在上面的代码中,register() 函数用来注册子应用,其中的配置项与主应用中的相同。bootstrap() 函数用来启动子应用,其中包含了初始化和渲染等过程。mount() 函数用来作为子应用加载时的入口,其中包含了注册、初始化、渲染等过程。unmount() 函数用来作为子应用卸载时的入口,其中包含了销毁子应用的过程。需要注意的是,如果子应用是独立运行的,即非通过主应用加载的,那么直接调用 bootstrap() 启动子应用即可。

2.5 全局状态管理

主应用可以通过 setGlobalState 方法来设置全局状态,子应用可以通过 onGlobalStateChange 方法来监听全局状态的变化。这样,当主应用修改了全局状态时,所有的子应用都可以得到通知并进行相应的处理。

在主应用中设置全局状态:
import { setGlobalState } from 'qiankun';

setGlobalState({ user: { name: 'lang', age: 18 } });
在子应用中监听全局状态的变化:
import { onGlobalStateChange, setGlobalState } from 'qiankun';

onGlobalStateChange((newState, prevousState) => {
  console.log('newState:', newState);
  console.log('prevousState:', prevousState);
});

// 修改全局状态
setGlobalState({ user: { name: 'xiaolang', age: 20 } });

2.6 子应用之间共享状态

子应用之间可以通过主应用作为中介来实现状态的共享。具体实现方式如下:

在主应用中设置共享状态
import { setGlobalState } from 'qiankun';

setGlobalState({ user: { name: 'lang', age: 18 } });
在主应用中将共享状态传递给子应用:
import { start } from 'qiankun';

const app = start({ 
  name: 'vue3',
  entry: '//localhost:8081',
  container: '#vue3',
  activeRule: '/vue3',
  props: {
    shared: { 
      getGlobalState: () => Promise.resolve(globalState),
      setGlobalState: (state) => Promise.resolve(setGlobalState(state)),
    }
  }
});
在子应用中使用共享状态:
import { initGlobalState } from 'qiankun';

const actions = initGlobalState({ user: { name: 'lang1', age: 19 } });

actions.onGlobalStateChange((newState, prevousState) => {
  console.log('newState:', newState);
  console.log('prevousState:', prevousState);
});

// 修改共享状态
actions.setGlobalState({ user: { name: 'lang2', age: 20 } });

上面,主应用通过 props 将 getGlobalState 和 setGlobalState 传递给了子应用。子应用可以通过 initGlobalState 方法来初始化全局状态,并且可以监听全局状态的变化。在子应用中,如果需要修改全局状态,可以通过 setGlobalState 方法来实现。

2.7 子应用如何调用其他子应用组件

Vue 子应用中调用 React子应用的组件

可以先在主应用中通过 registerMicroApps 方法注册好所有的子应用,并在主应用中管理子应用之间的通信。然后,可以在需要使用其他子应用组件的子应用中通过 loadMicroApp 方法异步加载对应的子应用并获取到对应子应用的实例,从而使用其提供的组件。

import { loadMicroApp } from 'qiankun';

const loadReactApp = () => loadMicroApp({
    name: 'reactApp',
    entry: '//localhost:8082',
    container: '#react',
    activeRule: '/react',
});

// 在需要使用 ReactApp 中的组件的 VueApp 组件中异步加载 ReactApp,并使用其提供的组件
const VueApp = {
  template: `
    <div>
      <h1>VueApp</h1>
      <react-component />
    </div>
  `,
  mounted() {
    loadReactApp().then(app => {
      const ReactComponent = app.getComponent('ReactComponent');
      Vue.component('react-component', {
        template: `<div><ReactComponent /></div>`,
      });
    });
  },
};

在 VueApp 的 mounted 生命周期中异步加载了 ReactApp 并获取到其实例,然后通过 getComponent 方法获取到了 ReactApp 中提供的 ReactComponent 组件,并将其转换为 Vue 组件供 VueApp 使用。

React 子应用中调用 Vue 子应用的组件

React 子应用中,加载 Vue 子应用并获取其组件:
import { loadMicroApp, getGlobalState } from 'qiankun';

function App() {
  const [vueComponent, setVueComponent] = useState(null);

  useEffect(() => {
    const vueApp = loadMicroApp({
      name: 'vue3',
      entry: '//localhost:8081',
      container: '#vue3',
      activeRule: '/vue3',
      props: { name: 'vue-app' }
    });

    vueApp.onGlobalStateChange((state, prev) => {
      console.log('[React] Vue global state changed: ', state);
    });

    vueApp
      .loadPromise.then(() => {
        const vueInstance = vueApp.bootstrap();
        setVueComponent(vueInstance.$children[0]);
      });

    return () => vueApp.unmount();
  }, []);

  return (
    <div>
      <h1>React App</h1>
      <hr />
      <h2>Vue App Component:</h2>
      {vueComponent && (
        <div>
          <p>{vueComponent.message}</p>
          <button onClick={() => vueComponent.handleClick()}>Click me!</button>
        </div>
      )}
    </div>
  );
}

export default App;
当然得在 Vue 子应用中,暴露一个组件,上面的vueInstance.$children[0]就是这个组件:
<template>
  <div>
    <p>{{ message }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: 'Hello from Vue!'
    };
  },
  methods: {
    handleClick() {
      console.log('Vue button clicked!');
    }
  }
};
</script>
在调用其他子应用的组件时,可以使用 props 传递参数,和在普通的 React 组件中传递 props 是类似的。具体来说,在调用时,可以将需要传递的参数放在 props 对象中,然后作为第二个参数传递给 render 方法,例如:
import React from 'react';
import { render, hydrate } from 'react-dom';
import { registerMicroApps, start } from 'qiankun';
import App from './App';

// 注册子应用
registerMicroApps([...]);

// 启动 qiankun
start();

// 在 App 组件中调用其他子应用的组件
function App() {
  return (
    <div>
      {/* 调用 Vue 子应用的组件 */}
      <div>
        {render('vue3', { name: 'Tom' }, { container: '#vue-container' })}
      </div>
    </div>
  );
}

// 渲染根组件
hydrate(<App />, document.getElementById('root'));

我们向 Vue 子应用的组件中传递了一个名为 name 的参数,并且将其渲染到了 #vue-container 容器中。在 Vue 子应用中,可以通过 props 对象获取这个参数,例如:
<template>
  <div>
    <h2>Vue Component</h2>
    <p>Hello, {{ name }}!</p>
  </div>
</template>

<script>
export default {
  name: 'VueComponent',
  props: {
    name: {
      type: String,
      required: true,
    },
  },
};
</script>

这样,在 Vue 子应用中就可以通过 this.$props.name 访问这个参数了

由于 Vue 和 React 使用了不同的渲染引擎,它们之间的组件并不完全兼容。我这个也是自己想尝试一下不同的语言他们互相调用,所以大家在实际开发中需要根据具体情况来选择使用哪种方式来实现组件之间的互相调用。一般的话还是vue调用vue, react调用react兼容比较好。

上面就是就是小浪介绍的一个简单的 qiankun 实例demo,包括主应用使用 React,子应用1 使用 Vue 3,子应用2 使用 React 。

3.我开发中遇到的坑点

在平时的开发肯定也不是一帆风顺的,也会多多少少遇到一些问题,下面是我平时遇到的一些问题,也查阅一些解决措施,这里就不一一详细把解决措施写出来,哈哈哈,写不完。

3.1 路由问题:

由于主应用和子应用都需要进行路由处理,需要注意路由的定义、匹配和传递,确保各个子应用之间的路由能够正确地被处理。

措施:统一路由管理:尽量采用相同的路由管理方式,确保路由的定义和匹配方式相同,方便进行传递和处理。

3.2 样式隔离问题:

由于不同的子应用之间可能会有样式冲突的问题,需要考虑如何进行样式隔离,以确保各个子应用之间的样式不会互相干扰。

措施:可以采用 css Modules、BEM 等方式进行样式隔离,或者使用 Shadow DOM 等技术进行样式隔离。在公司里后面全部改用CSS Modules。

3.3 全局状态管理问题:

由于各个子应用之间需要共享一些全局状态,需要考虑如何进行全局状态管理,以便于各个子应用之间能够共享数据。

措施:可以采用 Redux、Mobx 等全局状态管理库进行状态管理,或者使用 qiankun 提供的 props、emit 等方式进行状态传递。

3.4 生命周期问题:

由于 qiankun 微前端框架会对主应用和子应用进行生命周期管理,因此需要注意各个组件的生命周期方法的调用时机和顺序。

措施:可以在各个生命周期方法中进行必要的处理,或者使用 qiankun 提供的 emitLifeCycles 等方式进行生命周期管理。

3.5缓存问题:

由于 qiankun 微前端框架会对各个子应用进行缓存,需要注意缓存的清理和更新,以确保各个子应用之间的数据能够及时地同步更新

措施:可以使用 qiankun 提供的 prefetch、sandbox 等方式进行缓存管理,或者手动清理和更新缓存。

3.6 避免重复引入公共依赖:

在多个子应用之间共享组件时,应该避免重复引入公共依赖。一种解决方法是将公共依赖打包为一个单独的库,并将其发布到npm上,然后在子应用中通过npm依赖引入。另一种解决方法是将公共依赖打包为umd格式,并在qiankun的global对象上注册,使得其他子应用可以通过global对象来访问公共依赖。例如:

在主应用中注册公共依赖:

import React from 'react';
import ReactDOM from 'react-dom';
import MyComponent from 'my-component';

window.MyComponent = MyComponent;

在子应用中使用公共依赖:

import React from 'react';
import ReactDOM from 'react-dom';
import { useEffect } from 'react';

const { MyComponent } = window;

3.7 确保应用能够正确注册

这可能是我自己会遇到的问题,有时候因为自己拼错单词,导致没有注册成功.....应用注册是一个关键步骤,需要确保主应用和子应用都能够正确地注册到qiankun微前端框架中。在主应用中,应该正确地调用registerMicroApps函数,并确保子应用的entry和container属性都被正确地设置。在子应用中,应该正确地调用start函数,并确保子应用的mount函数能够正确地渲染子应用。

4.结语:

有的时候查找微前端相关的问题的时候,下面总有言论,不看好微前端

确实,微前端需要解决许多复杂的问题,例如应用程序的拆分、应用程序之间的通信和数据共享等。这会增加开发和维护的复杂性,可能需要额外的学习成本和工具支持。微前端应用程序可能需要通过网络加载许多小块代码,这可能会影响应用程序的性能和用户体验。此外,微前端还需要在运行时处理一些额外的逻辑,例如路由和状态管理,微前端应用程序可能需要跨域通信和数据共享,这可能会引入一些安全风险。此外,微前端还需要确保应用程序的隔离和沙箱,以防止应用程序之间的影响和攻击。技术选型:微前端需要处理不同的技术栈和框架之间的兼容性和交互性。这可能需要额外的工作。等等问题。

虽然微前端存在一些挑战和限制,但随着技术的发展和社区的支持,这些问题将得到解决,微前端也将成为构建大型前端应用程序的一种有力选择。总之,qiankun 是一个非常优秀的微前端解决方案,它可以帮助我们更好地管理和维护前端应用程序,提高应用程序的可扩展性和可维护性。同时,qiankun 也是一个非常灵活和可扩展的框架,可以适用于各种前端应用程序的开发场景。

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK