feat(router): 重构路由模块并优化页面加载逻辑
- 引入 ScriptManager、TimerManager 和 EventManager 统一管理资源 - 路由配置支持默认重定向路径 '/' - 移除旧版 scriptLoader 与 routeUtils,使用新的模块化加载机制 - 增加路由生命周期控制,自动清理定时器和事件监听器 - 改进点击代理逻辑,增强按钮激活状态切换 - 页面切换时卸载旧脚本及资源,防止内存泄漏 - 修复初始化日志冗余问题,提升代码可维护性
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
export class EventManager {
|
||||
constructor() {
|
||||
this.listeners = [];
|
||||
}
|
||||
|
||||
add(ele, type, handler, options) {
|
||||
ele.addEventListener(type, handler, options);
|
||||
this.listeners.push({ ele, type, handler, options });
|
||||
}
|
||||
|
||||
remove(ele, type, handler, options) {
|
||||
ele.removeEventListener(type, handler, options);
|
||||
this.listeners = this.listeners.filter(
|
||||
(listener) =>
|
||||
listener.ele !== ele ||
|
||||
listener.type !== type ||
|
||||
listener.handler !== handler ||
|
||||
listener.options !== options
|
||||
);
|
||||
}
|
||||
|
||||
removeAll() {
|
||||
this.listeners.forEach((listener) => {
|
||||
listener.ele.removeEventListener(
|
||||
listener.type,
|
||||
listener.handler,
|
||||
listener.options
|
||||
);
|
||||
});
|
||||
this.listeners = [];
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/**
|
||||
* 获取路由的路径和详细参数
|
||||
* @returns
|
||||
*/
|
||||
export function getParamsUrl() {
|
||||
// 获取路由
|
||||
const hasDetail = location.hash.split('?');
|
||||
// 获取路由名称
|
||||
const hasName = hasDetail[0].split('#')[1];
|
||||
// 获取请求参数
|
||||
const params = hasDetail[1] ? hasDetail[1].split('&') : [];
|
||||
// 解析请求参数
|
||||
let query = {};
|
||||
for (let i = 0; i < params.length; i++) {
|
||||
let param = params[i].split('=');
|
||||
query[param[0]] = param[1];
|
||||
}
|
||||
|
||||
return {
|
||||
path: hasName,
|
||||
query: query,
|
||||
params: params,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 闭包返回函数
|
||||
* @param {*} name
|
||||
* @returns
|
||||
*/
|
||||
export function closure(name) {
|
||||
return (currentHash) => {
|
||||
window.name && window[name](currentHash);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成随机key
|
||||
* @returns
|
||||
*/
|
||||
export function genKey() {
|
||||
const KEY_TEMPLATE = 'xxxxxxxx';
|
||||
return KEY_TEMPLATE.replace(/[xy]/g, (c) => {
|
||||
const r = (Math.random() * 16) | 0;
|
||||
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
let currentScript = null;
|
||||
export function loadScript(path, globalInitName) {
|
||||
console.log('load script', currentScript);
|
||||
|
||||
if (currentScript) {
|
||||
console.log('remove script')
|
||||
currentScript.remove();
|
||||
currentScript = null;
|
||||
}
|
||||
|
||||
if (globalInitName && window[globalInitName]) {
|
||||
delete window[globalInitName];
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script');
|
||||
script.src = path;
|
||||
script.type = 'module';
|
||||
|
||||
script.onload = () => {
|
||||
currentScript = document.querySelector(`script[src='${path}']`);
|
||||
console.log('script loaded', currentScript);
|
||||
resolve();
|
||||
};
|
||||
|
||||
script.onerror = reject;
|
||||
|
||||
document.body.appendChild(script);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
export class ScriptManager {
|
||||
constructor() {
|
||||
this.currentScriptEl = null;
|
||||
this.currentModule = null;
|
||||
this.currentName = null;
|
||||
}
|
||||
|
||||
async loadScript({ path, name, isModule = false, deps = [] }) {
|
||||
// 卸载现脚本
|
||||
await this.unloadScript();
|
||||
|
||||
for (const dep of deps) {
|
||||
await this._appendScript({ path: dep, isModule: false });
|
||||
}
|
||||
|
||||
if (isModule) {
|
||||
const absPath = (path = '?t' + Date.now());
|
||||
this.currentModule = await import(absPath);
|
||||
this.currentScriptEl = null;
|
||||
this.currentName = name;
|
||||
return this.currentModule;
|
||||
} else {
|
||||
await this._appendScript({ path, isModule: false });
|
||||
this.currentName = name;
|
||||
return window[name] || null;
|
||||
}
|
||||
}
|
||||
|
||||
async _appendScript({ path, isModule }) {
|
||||
const s = document.createElement('script');
|
||||
try {
|
||||
s.src = path + '?t=' + Date.now();
|
||||
s.type = isModule ? 'module' : 'text/javascript';
|
||||
s.async = false;
|
||||
s.onload = () => {
|
||||
this.currentScriptEl = s;
|
||||
};
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to load script: ${path}`);
|
||||
} finally {
|
||||
document.body.appendChild(s);
|
||||
}
|
||||
}
|
||||
|
||||
async unloadScript() {
|
||||
try {
|
||||
if (this.currentName && window[this.currentName]) {
|
||||
const mod = window[this.currentName];
|
||||
const unName = this._findUnmountName(mod);
|
||||
if (unName && typeof mod[unName] === 'function') {
|
||||
await mod[unName]();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.currentModule) {
|
||||
const mod = this.currentModule;
|
||||
const unName = this._findUnmountName(mod);
|
||||
if (unName && typeof mod[unName] === 'function') {
|
||||
await mod[unName]();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
if (this.currentScriptEl) {
|
||||
try {
|
||||
this.currentScriptEl.remove();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
this.currentModule = null;
|
||||
this.currentName = null;
|
||||
}
|
||||
|
||||
_findInitName(obj) {
|
||||
if (!obj) return null;
|
||||
const candidates = ['init', 'start', 'main'];
|
||||
for (const c of candidates) {
|
||||
if (typeof obj[c] === 'function') {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
_findUnmountName(obj) {
|
||||
if (!obj) return null;
|
||||
const candidates = ['unmount', 'stop', 'destroy'];
|
||||
for (const c of candidates) {
|
||||
if (typeof obj[c] === 'function') {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async runInit(name) {
|
||||
if (this.currentModule) {
|
||||
const mod = this.currentModule;
|
||||
const init = this._findInitName(mod);
|
||||
if (init) return mod[init]();
|
||||
return null;
|
||||
} else if (name && window[name]) {
|
||||
const mod = window[name];
|
||||
const init = this._findInitName(mod);
|
||||
if (init) return mod[init]();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
export class TimerManager {
|
||||
constructor() {
|
||||
// 定时器对象数组
|
||||
this.timers = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加定时器到数组中
|
||||
* @param {*} fn
|
||||
* @param {*} delay
|
||||
* @param {...any} args
|
||||
* @returns
|
||||
*/
|
||||
setTimeout(fn, delay, ...args) {
|
||||
const id = window.setTimeout(fn, delay, ...args);
|
||||
this.timers.push(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加定时器到数组中
|
||||
* @param {*} fn
|
||||
* @param {*} delay
|
||||
* @param {...any} args
|
||||
* @returns
|
||||
*/
|
||||
setInterval(fn, delay, ...args) {
|
||||
const id = window.setInterval(fn, delay, ...args);
|
||||
this.timers.push(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除定时器
|
||||
* @param {*} id
|
||||
*/
|
||||
clearTimeout(id) {
|
||||
window.clearTimeout(id);
|
||||
this.timers = this.timers.filter((timerId) => timerId !== id);
|
||||
}
|
||||
|
||||
clearInterval(id) {
|
||||
window.clearInterval(id);
|
||||
this.timers = this.timers.filter((timerId) => timerId !== id);
|
||||
}
|
||||
|
||||
cleanAll() {
|
||||
this.timers.forEach((timerId) => {
|
||||
window.clearTimeout(timerId);
|
||||
window.clearInterval(timerId);
|
||||
});
|
||||
this.timers = [];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user