qiankun
内部通过监听浏览器的路由变化事件(如 popstate
和 hashchange
)来管理子应用的激活和加载。以下是 qiankun
如何实现这一功能的详细解释:
先了解下一些基本知识点:
popstate
和 hashchange
是浏览器中用于监听 URL 变化的两个不同事件。它们分别用于不同的路由模式(history
模式和 hash
模式)。以下是这两个事件的详细解释和使用场景:
popstate
事件
用途:
- 主要用于监听浏览器历史记录的变化,通常在使用
history
模式时使用。 - 当用户点击浏览器的前进或后退按钮,或者通过 JavaScript 调用
history.pushState
或history.replaceState
时触发。
触发条件:
- 用户点击浏览器的前进或后退按钮。
- 调用
history.pushState
或history.replaceState
方法。
示例代码:
javascript
window.addEventListener('popstate', (event) => {
console.log('popstate event triggered');
console.log('Current URL:', window.location.href);
// 处理路由变化逻辑
checkActiveApps();
});
hashchange
事件
用途:
- 主要用于监听 URL 的
hash
部分的变化,通常在使用hash
模式时使用。 - 当 URL 的
hash
部分发生变化时触发。
触发条件:
- URL 的
hash
部分发生变化。 - 例如,从
http://example.com/#/app1
变为http://example.com/#/app2
。
示例代码:
javascript
window.addEventListener('hashchange', (event) => {
console.log('hashchange event triggered');
console.log('Current URL:', window.location.href);
// 处理路由变化逻辑
checkActiveApps();
});
startsWith(rule)
是 JavaScript 字符串方法,用于检查一个字符串是否以指定的子字符串开头。它返回一个布尔值:如果字符串以指定的子字符串开头,则返回 true
,否则返回 false
。在 qiankun
中,startsWith(rule)
通常用于匹配当前路由路径和子应用的 activeRule
。以下是一个简化的示例,展示如何在 qiankun
中使用 startsWith(rule)
来检查路由是否匹配子应用的 activeRule
。
基本用法
ini
const str = "Hello, world!";
const rule = "Hello";
console.log(str.startsWith(rule)); // true
实现细节
-
注册事件监听器:
qiankun
在启动时会注册popstate
和hashchange
事件监听器。- 这些监听器会捕获路由变化,并根据
activeRule
来决定哪些子应用需要被激活。
-
检查
activeRule
:- 当路由变化时,
qiankun
会遍历所有注册的子应用配置,检查当前路由是否匹配任何子应用的activeRule
。 - 如果匹配,则激活相应的子应用。
- 当路由变化时,
-
激活子应用:
- 激活子应用包括加载子应用的资源、调用子应用的生命周期钩子(如
bootstrap
和mount
)等。
- 激活子应用包括加载子应用的资源、调用子应用的生命周期钩子(如
以下是 qiankun
内部监听路由变化的简化代码示例:
scss
let activeApps = [];
let registeredApps = [];
function checkActiveApps() {
const currentPath = window.location.pathname;
registeredApps.forEach(app => {
if (matchPath(currentPath, app.activeRule)) {
if (!activeApps.includes(app)) {
activateApp(app);
activeApps.push(app);
}
} else {
if (activeApps.includes(app)) {
deactivateApp(app);
activeApps = activeApps.filter(a => a !== app);
}
}
});
}
function matchPath(path, rule) {
// 使用 startsWith 检查路径是否以规则开头
return path.startsWith(rule);
}
function activateApp(app) {
// 加载子应用资源
loadApp(app);
// 调用子应用的 bootstrap 和 mount 生命周期钩子
app.bootstrap();
app.mount();
}
function deactivateApp(app) {
// 调用子应用的 unmount 生命周期钩子
app.unmount();
}
function loadApp(app) {
fetch(app.entry)
.then(response => response.text())
.then(html => {
const container = document.querySelector(app.container);
container.innerHTML = html;
});
}
function registerMicroApps(apps) {
registeredApps = apps;
}
function start() {
// 注册事件监听器
window.addEventListener('popstate', checkActiveApps);
window.addEventListener('hashchange', checkActiveApps);
// 初始检查
checkActiveApps();
}
// 示例注册子应用
registerMicroApps([
{
name: 'app1',
entry: 'https://server1.com/app1',
container: '#app1',
activeRule: '/app1',
bootstrap: () => console.log('app1 bootstraped'),
mount: () => console.log('app1 mounted'),
unmount: () => console.log('app1 unmounted'),
},
{
name: 'app2',
entry: 'https://server2.com/app2',
container: '#app2',
activeRule: '/app2',
bootstrap: () => console.log('app2 bootstraped'),
mount: () => console.log('app2 mounted'),
unmount: () => console.log('app2 unmounted'),
},
]);
start();
详细步骤
-
注册子应用:
- 使用
registerMicroApps
注册子应用配置。 - 每个子应用的配置包括
name
、entry
、container
和activeRule
。
javascriptregisterMicroApps([ { name: 'app1', entry: 'https://server1.com/app1', container: '#app1', activeRule: '/app1', bootstrap: () => console.log('app1 bootstraped'), mount: () => console.log('app1 mounted'), unmount: () => console.log('app1 unmounted'), }, { name: 'app2', entry: 'https://server2.com/app2', container: '#app2', activeRule: '/app2', bootstrap: () => console.log('app2 bootstraped'), mount: () => console.log('app2 mounted'), unmount: () => console.log('app2 unmounted'), }, ]);
- 使用
-
启动
qiankun
:- 调用
start
函数启动qiankun
。 start
函数会注册popstate
和hashchange
事件监听器,并进行初始检查。
scssfunction start() { // 注册事件监听器 window.addEventListener('popstate', checkActiveApps); window.addEventListener('hashchange', checkActiveApps); // 初始检查 checkActiveApps(); } start();
- 调用
-
检查激活的子应用:
- 当路由变化时,
checkActiveApps
函数会被调用。 - 该函数会遍历所有注册的子应用配置,检查当前路由是否匹配任何子应用的
activeRule
。
inifunction checkActiveApps() { const currentPath = window.location.pathname; registeredApps.forEach(app => { if (matchPath(currentPath, app.activeRule)) { if (!activeApps.includes(app)) { activateApp(app); activeApps.push(app); } } else { if (activeApps.includes(app)) { deactivateApp(app); activeApps = activeApps.filter(a => a !== app); } } }); }
- 当路由变化时,
-
路径匹配:
matchPath
函数使用startsWith(rule)
来检查当前路径是否以activeRule
开头。
javascriptfunction matchPath(path, rule) { // 使用 startsWith 检查路径是否以规则开头 return path.startsWith(rule); }
-
激活子应用:
- 如果路由匹配某个子应用的
activeRule
,activateApp
函数会被调用。 - 该函数会加载子应用的资源,并调用子应用的
bootstrap
和mount
生命周期钩子。
scssfunction activateApp(app) { // 加载子应用资源 loadApp(app); // 调用子应用的 bootstrap 和 mount 生命周期钩子 app.bootstrap(); app.mount(); }
- 如果路由匹配某个子应用的
-
加载子应用资源:
loadApp
函数会根据子应用的entry
配置加载子应用的资源。- 例如,加载
https://server1.com/app1/index.html
及其相关资源。
inifunction loadApp(app) { fetch(app.entry) .then(response => response.text()) .then(html => { const container = document.querySelector(app.container); container.innerHTML = html; }); }
下面是对 fetch(app.entry).then(response => response.text()).then(html => { ... })
这段代码的详细解析。
代码解析
ini
fetch(app.entry)
.then(response => response.text())
.then(html => {
const container = document.querySelector(app.container);
container.innerHTML = html;
});
详细步骤
-
发起 HTTP 请求:
fetch(app.entry)
:使用fetch
API 发起一个 HTTP GET 请求,app.entry
是子应用的入口地址(例如//localhost:8080
)。
-
处理响应:
.then(response => response.text())
:将响应体解析为文本格式。response.text()
返回一个 Promise,解析后的结果是响应的文本内容。
-
处理 HTML 内容:
.then(html => { ... })
:将解析后的 HTML 内容传递给回调函数。const container = document.querySelector(app.container)
:使用document.querySelector
选择子应用的挂载容器。app.container
是子应用挂载的 DOM 节点选择器(例如#container
)。container.innerHTML = html
:将解析后的 HTML 内容设置为容器的innerHTML
,从而将子应用的 HTML 渲染到指定的容器中。
详细流程图
ini
+-------------------+
| fetch(app.entry) |
| - 发起 HTTP 请求 |
+-------------------+
|
v
+-------------------+
| response => |
| response.text() |
| - 解析响应为文本 |
+-------------------+
|
v
+-------------------+
| html => { |
| const container = |
| document.querySelector(app.container); |
| container.innerHTML = html; |
| - 渲染 HTML 到容器 |
+-------------------+
示例
假设 app.entry
是 //localhost:8080
,app.container
是 #container
,并且 //localhost:8080
返回以下 HTML 内容:
xml
<!DOCTYPE html>
<html>
<head>
<title>App 1</title>
</head>
<body>
<h1>Hello from App 1</h1>
</body>
</html>
代码执行流程
-
发起 HTTP 请求:
scssfetch('//localhost:8080')
-
处理响应:
ini.then(response => response.text())
- 假设响应体是上述 HTML 内容。
-
处理 HTML 内容:
ini.then(html => { const container = document.querySelector('#container'); container.innerHTML = html; });
container
是#container
元素。container.innerHTML = html
将上述 HTML 内容设置为#container
的内容。
可能出现的问题及其解决方案
-
网络请求失败:
-
问题 :
fetch
请求失败,例如网络问题或服务器错误。 -
解决方案:
-
使用
catch
处理错误。 -
示例:
inifetch(app.entry) .then(response => response.text()) .then(html => { const container = document.querySelector(app.container); container.innerHTML = html; }) .catch(error => { console.error('Fetch error:', error); });
-
-
-
响应格式不正确:
-
问题:响应体不是预期的 HTML 内容。
-
解决方案:
-
检查响应状态码。
-
示例:
inifetch(app.entry) .then(response => { if (!response.ok) { throw new Error('Network response was not ok ' + response.statusText); } return response.text(); }) .then(html => { const container = document.querySelector(app.container); container.innerHTML = html; }) .catch(error => { console.error('Fetch error:', error); });
-
-
-
容器选择器错误:
-
问题 :
app.container
选择器不正确或容器不存在。 -
解决方案:
-
确保选择器正确且容器存在。
-
示例:
inifetch(app.entry) .then(response => response.text()) .then(html => { const container = document.querySelector(app.container); if (container) { container.innerHTML = html; } else { console.error('Container not found:', app.container); } }) .catch(error => { console.error('Fetch error:', error); });
-
-
-
安全性问题:
-
问题 :直接设置
innerHTML
可能导致 XSS 攻击。 -
解决方案:
-
使用安全的模板引擎或手动处理 HTML 内容。
-
示例:
inifetch(app.entry) .then(response => response.text()) .then(html => { const container = document.querySelector(app.container); if (container) { // 使用安全的模板引擎或手动处理 HTML container.innerHTML = sanitizeHTML(html); } else { console.error('Container not found:', app.container); } }) .catch(error => { console.error('Fetch error:', error); }); function sanitizeHTML(input) { const div = document.createElement('div'); div.textContent = input; return div.innerHTML; }
-
-
通过上述详细解析,你可以更好地理解 fetch(app.entry).then(response => response.text()).then(html => { ... })
的工作原理和代码实现。
-
卸载子应用:
- 如果路由不再匹配某个子应用的
activeRule
,deactivateApp
函数会被调用。 - 该函数会调用子应用的
unmount
生命周期钩子。
scssfunction deactivateApp(app) { // 调用子应用的 unmount 生命周期钩子 app.unmount(); }
- 如果路由不再匹配某个子应用的
总结
startsWith(rule)
:用于检查字符串是否以指定的子字符串开头,返回布尔值。qiankun
中的使用 :在matchPath
函数中使用startsWith(rule)
来检查当前路由路径是否以子应用的activeRule
开头。- 路由变化监听 :通过监听
popstate
和hashchange
事件,qiankun
能够自动检查路由变化,并根据activeRule
动态激活和加载子应用。
通过这种方式,qiankun
能够有效地管理子应用的激活和加载,确保在路由变化时正确地加载和卸载子应用。