跳到主要内容

认识

一、认识


Single-Spa 是一个用于构建微前端架构的框架,它允许你将多个独立的前端应用(即子应用)集成在同一个页面中,并对它们的生命周期进行管理。single-spa 通过监听 url change 事件,在路由变化时匹配到渲染的子应用并进行渲染,这个思路也是目前实现微前端的主流方式。同时single-spa要求子应用修改渲染逻辑并暴露出三个方法:bootstrapmountunmount,分别对应初始化、渲染和卸载,这也导致子应用需要对入口文件进行修改。但是, Single-spa 主要专注于微前端架构中应用的生命周期管理和协调加载,它本身并不提供完全的沙箱或隔离机制。

二、特点


三、问题


3.1 认识 SingleSpa?

Single-Spa 是一个用于构建微前端架构的框架,它允许你将多个独立的前端应用(即子应用)集成在同一个页面中,并对它们的生命周期进行管理。single-spa 通过监听 url change 事件,在路由变化时匹配到渲染的子应用并进行渲染,这个思路也是目前实现微前端的主流方式。同时single-spa要求子应用修改渲染逻辑并暴露出三个方法:bootstrapmountunmount,分别对应初始化、渲染和卸载,这也导致子应用需要对入口文件进行修改。但是, Single-spa 主要专注于微前端架构中应用的生命周期管理和协调加载,它本身并不提供完全的沙箱或隔离机制。

3.2 SingleSpa 是如何实现的?

SingleSpa 是一个用于构建微前端架构的框架,它允许你将多个独立的前端应用(即子应用)集成在同一个页面中,并对它们的生命周期进行管理。Single-spa 并不内置完善的隔离机制, 它的重点在于调度和管理微应用的加载、卸载和生命周期。SingleSpa 的实现原理

一、应用注册与配置:

  1. 注册应用, SingleSpa 提供了 registerApplication 接口,用于注册微应用。每个微应用需要配置以下内容: 应用名称, 唯一标识该应用; 加载函数loadApp), 返回一个 Promise,加载微应用的代码(通常返回一个包含生命周期函数的模块); 激活规则(activeWhen, 一个函数或数组,用来判断当前 URL 是否满足激活该应用的条件; 自定义属性(customProps, 传递给微应用的自定义配置,可选。

  2. 生命周期, 每个微应用都需要实现一组标准的生命周期方法: bootstrap, 应用初始化阶段,只执行一次,用于设置应用初始状态; mount, 将应用挂载到 DOM 上,应用进入活跃状态; unmount, 将应用从 DOM 卸载,进入非活跃状态; update, 用于应用已经挂载后,接收更新(例如路由参数变化);

二、路由监控与激活判定: 1. 全局路由监听, SingleSpa 在内部通过监听浏览器路由变化(如 popstatehashchange 以及通过 history API 触发的变化),捕获 URL 变化事件; 2. 激活判断, 每当路由变化时,SingleSpa 遍历已注册的应用,根据它们的 activeWhen 规则判断哪些应用应当处于激活状态。对于当前不活跃的已挂载应用,会调用其 unmount 方法。对于满足激活条件但未加载或未挂载的应用,会按顺序执行加载、初始化(bootstrap)、挂载(mount)流程。

三、生命周期管理:

  1. 异步流程管理, 由于各个生命周期方法可能是异步的,SingleSpa 采用 Promise 机制来确保生命周期之间顺序正确: 1. 加载(loadApp, 获取应用代码模块; 2. 初始化(bootstrap, 调用模块的 bootstrap 方法,初始化一次; 3. 挂载(mount, 调用 mount 方法,将应用渲染到页面中。4. 卸载(unmount, 当应用不再活跃时,调用 unmount 方法。

  2. 错误处理与隔离, 每个生命周期步骤都应捕获错误,防止某个子应用出错影响整个页面。SingleSpa 也提供了相关 API 供开发者处理错误。

3.3 如果让你模拟 SingleSpa, 该怎么实现?

一、构建应用注册机制: 创建一个全局注册表来保存所有微应用的配置信息

const registeredApps = [];

function registerApplication(name, loadApp, activeWhen, customProps = {}) {
registeredApps.push({
name,
loadApp,
activeWhen,
customProps,
status: 'NOT_LOADED', // NOT_LOADED, BOOTSTRAPPED, MOUNTED, UNMOUNTED
bootstrap: null,
mount: null,
unmount: null
});
}

二、监听路由变化, 监听 popstatehashchange 事件(也可以重写 history.pushState 以捕获编程式路由变化)

function handleRouteChange() {
// 判断哪些应用应该激活
const activeApps = registeredApps.filter(app => app.activeWhen(window.location));

// 卸载不再活跃的应用
registeredApps.forEach(app => {
if (app.status === 'MOUNTED' && !app.activeWhen(window.location)) {
app.unmount(app.customProps).then(() => {
app.status = 'UNMOUNTED';
}).catch(err => console.error(`Error unmounting ${app.name}:`, err));
}
});

// 加载并挂载新的激活应用
activeApps.forEach(app => {
if (app.status === 'NOT_LOADED' || app.status === 'UNMOUNTED') {
app.loadApp().then(module => {
// 假设 module 包含 bootstrap, mount, unmount 方法
app.bootstrap = module.bootstrap;
app.mount = module.mount;
app.unmount = module.unmount;
return app.bootstrap(app.customProps);
}).then(() => {
return app.mount(app.customProps);
}).then(() => {
app.status = 'MOUNTED';
}).catch(err => console.error(`Error mounting ${app.name}:`, err));
}
});
}

window.addEventListener('popstate', handleRouteChange);
window.addEventListener('hashchange', handleRouteChange);

三、初始化启动, 在页面加载时,调用一次 handleRouteChange 来根据当前 URL 挂载需要的应用:

// 页面加载时执行一次路由检查
handleRouteChange();

四、示例微应用

// 假设这是一个微应用模块(例如 remoteApp.js)
export function bootstrap(props) {
console.log('Bootstrap with', props);
return Promise.resolve();
}

export function mount(props) {
console.log('Mounting app with', props);
// 渲染到 DOM 中
const container = document.getElementById('micro-app');
container.innerHTML = `<div>Micro App Content</div>`;
return Promise.resolve();
}

export function unmount(props) {
console.log('Unmounting app with', props);
const container = document.getElementById('micro-app');
container.innerHTML = '';
return Promise.resolve();
}

通过 registerApplication 注册这个微应用,并设置激活条件(例如 URL 包含 /app 时激活):

registerApplication(
'my-micro-app',
() => import('./remoteApp.js'),
(location) => location.pathname.startsWith('/app'),
{ someProp: 'value' }
);

这样,一个基本的 SingleSpa 模拟就完成了。核心思路在于:

  • 注册微应用及其生命周期函数。

  • 监听路由变化,根据激活条件进行应用加载、初始化、挂载或卸载。

  • 使用 Promise 管理异步生命周期函数,确保顺序正确并处理错误。

参考资料