网络状态监听与模拟
离线优先应用的核心假设是“网络随时可能中断”,因此应用必须能够实时感知网络状态变化并做出正确响应。本章聚焦前端侧的网络状态监听机制:navigator.onLine 属性、Network Information API 以及 online/offline 事件,并说明如何在集成测试中模拟这些状态变化。
4.1 核心原理
浏览器提供三种互补的网络状态感知方式:
| 机制 | 类型 | 能力 | 局限性 |
|---|---|---|---|
navigator.onLine |
同步属性 | 布尔值,表示当前是否连接网络 | 仅反映“是否有连接”,不区分网络质量;Android Chrome 中可能不准确 |
online / offline 事件 |
事件驱动 | 在连接状态变化时触发,可在 window 或 document 上监听 |
事件触发时机可能延迟;无法获取当前网络类型 |
Network Information API( navigator.connection) |
异步 + 事件 | 提供 effectiveType、downlink、rtt、saveData,并派发 change 事件 |
并非所有浏览器支持(Safari 不支持);需要处理 polyfill |
关键原则:不应依赖
navigator.onLine 做单次检查,而应组合使用事件监听与 Network Information API 实现全面覆盖。
4.2 实现流程
- 初始化检测:在应用启动时读取
navigator.onLine确定初始状态,同时注册online/offline事件监听。 - 事件响应:在事件回调中更新全局状态(如 Redux store、Context),触发 UI 重新渲染。
- 网络质量感知:若浏览器支持
navigator.connection,读取effectiveType('slow-2g'、'2g'、'3g'、'4g')并监听change事件,动态调整资源加载策略。 - 测试模拟:在集成测试中,使用 Chrome DevTools 或 Playwright / Puppeteer 的 CDP 命令模拟离线、慢速网络。
- 清理:在组件卸载或页面切换时移除事件监听,防止内存泄漏。
4.3 代码实现示例
基础网络状态监听器
// networkStatus.js
let isOnline = navigator.onLine;
let effectiveType = 'unknown';
function getConnection() {
return navigator.connection || navigator.mozConnection || navigator.webkitConnection;
}
function updateNetworkStatus() {
isOnline = navigator.onLine;
const conn = getConnection();
if (conn) {
effectiveType = conn.effectiveType; // 'slow-2g','2g','3g','4g'
}
console.log(`Network status: ${isOnline ? 'online' : 'offline'}, type: ${effectiveType}`);
// 触发自定义事件或更新状态管理
window.dispatchEvent(new CustomEvent('networkchange', {
detail: { isOnline, effectiveType }
}));
}
// 初始化
updateNetworkStatus();
// 监听浏览器事件
window.addEventListener('online', updateNetworkStatus);
window.addEventListener('offline', updateNetworkStatus);
// 监听 Network Information API 变化
const conn = getConnection();
if (conn) {
conn.addEventListener('change', updateNetworkStatus);
}
// 导出供测试清理
export function cleanup() {
window.removeEventListener('online', updateNetworkStatus);
window.removeEventListener('offline', updateNetworkStatus);
if (conn) conn.removeEventListener('change', updateNetworkStatus);
}
测试提示:在 Jest / Vitest 单元测试中,可使用
Object.defineProperty(navigator, 'onLine', { value: false }) 模拟离线,但更推荐在集成测试中使用浏览器原生模拟工具。
4.4 调试与模拟
Chrome DevTools 提供最直接的离线模拟:打开 DevTools > Network 标签 > 勾选 Offline。也可通过下拉菜单选择预设网络条件(Slow 3G、Fast 3G 等):
// 使用 Puppeteer 编程模拟
await page.evaluate(() => {
// 用 CDP 模拟网络 (实际使用 page.emulateNetworkConditions)
});
// Playwright 示例
await page.context().setOffline(true);
// 或使用 CDP 会话自定义延迟
const cdp = await page.context().newCDPSession(page);
await cdp.send('Network.emulateNetworkConditions', {
offline: false,
latency: 300,
downloadThroughput: 500 * 1024, // 500Kbps
uploadThroughput: 100 * 1024
});
常用模拟场景列表:
- 完全离线
- 不可靠网络(间歇断开)
- 慢速 2G (50Kbps)
- 慢速 3G (750Kbps)
- 固定延迟(如 300ms)
- 丢包率模拟(需通过代理工具如 Charles)
4.5 常见错误与陷阱
| 错误 | 后果 | 解决方案 |
|---|---|---|
仅依赖 navigator.onLine 同步判断 |
在某些系统(如 macOS Chrome)离线时仍返回 true,导致缓存逻辑失效 |
结合事件监听,并定期发送心跳请求确认可达性 |
| 事件监听重复注册 | 触发多次回调,引发重复状态更新或死循环 | 使用单例模式;在 React 中通过 useEffect 的清理函数移除监听 |
| 未处理 Safari 的兼容性 | 在 iOS 上无法获取连接类型,无 change 事件 |
检查 navigator.connection 是否存在,并提供 fallback |
| 测试中模拟离线后未恢复 | 后续测试用例仍处于离线状态,导致误报失败 | 在 beforeEach / afterEach 中重置网络条件 |
4.6 性能与稳定性优化
- 节流高频事件:部分浏览器可能在网络抖动时频繁触发
change或online/offline事件,建议使用 requestAnimationFrame 或setTimeout(fn, 500)合并响应。 - 延迟初始化:如果
navigator.connection的读取开销较大(极少情况),可延迟到应用首次需要网络质量信息时再绑定。 - 跨页面保持状态:对于 SPA,全局状态(如 Redux
isOnline)应在路由切换时不重置;对于 MPA,可用 ServiceWorker 的MessageChannel传递状态。 - 自动化测试稳定性:使用 Playwright 的
page.waitForEvent('offline')确保事件已触发后再断言 UI 变化,避免竞态条件。
4.7 网络状态监听流程
稳定性警告:部分低端 Android 设备在硬件网络切换(WiFi → 4G)时可能不触发
offline 事件,建议应用层加入心跳探活(如每 30s 发 HEAD 请求)作为补充。