前端错误边界 白屏是怎么被拦住的
红蓝扣风
5 min read
前端错误边界 白屏是怎么被拦住的
白屏不是"页面崩了",而是渲染过程中抛出的异常没被接住,导致整棵组件树没能挂上 DOM。拦住白屏靠两条线:框架级的错误边界 + 全局兜底,二者覆盖的范围不同,缺一不可。只做其中一条,都会留下一大片"用户看到空白、开发一无所知"的盲区。
错误边界只能兜渲染
错误边界是一个包裹组件,能捕获其子组件在渲染阶段抛出的异常,并渲染降级 UI 而非让整页空白。典型实现是在组件生命周期里记下错误并切换展示:
class ErrorBoundary extends Component {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error, info) {
reportError(error, info); // 上报到监控
}
render() {
if (this.state.hasError) return <Fallback />;
return this.props.children;
}
}
它只覆盖"渲染期"的子树异常。事件处理器、异步回调、自身渲染时的错误都兜不住,这是它最容易被误解的地方。经验上,错误边界应当包在路由层级(每个页面一个)和全局层级(包住整个应用)两层,前者让单个页面崩溃不影响其他页面,后者做最终兜底。
全局兜底:catch 不到的那部分
渲染之外的错误要交给 window 级监听:
window.addEventListener('error', (e) => reportError(e.error));
window.addEventListener('unhandledrejection', (e) => reportError(e.reason));
前者捕获同步未处理异常,后者捕获 Promise 未 catch 的拒绝。把这两者接上上报通道,至少能把"用户看到白屏但开发一无所知"的情况变成可追查的日志。边界:行内脚本或跨域脚本抛错时,error 事件拿到的信息可能被浏览器脱敏(只给 Script error.),需要给脚本加 crossorigin 并在服务端返回正确的 CORS 头才能拿到完整堆栈。资源加载失败(如图片、脚本 404)不走 error 的异常分支,而是触发带 target 的 error 事件,需要单独判断 e.target 是否为元素。
白屏检测:主动发现而非等反馈
错误边界挡住的是"已知异常",还有一类是渲染没抛错但关键节点没挂上 DOM。可在首屏后做探针:检查根容器是否长时间无子节点,或某个关键挂载点没被打上预期标记,触发降级渲染。
setTimeout(() => {
if (!rootEl.firstChild) renderFallback(rootEl);
}, 3000);
这种做法能在"静默白屏"时至少弹出一个可交互的兜底页,而不是让用户对着空白发呆。更进一步可用 MutationObserver 监听根节点是否在预期时间内出现过变化,比固定定时器更准。
几个边界
- 错误边界是组件,无法捕获自身 render 里的错误,需要再外层再包一层。
- 异步错误(setTimeout、接口回调)不经过边界,必须靠 window 监听或手动 try/catch。
- 降级 UI 本身要极简,不要依赖可能再次出错的复杂组件,否则会陷入"降级又崩"的循环,最稳妥是纯静态结构。
- 上报要带堆栈、路由、用户操作轨迹,否则线上白屏很难复现;同时要防上报本身出错导致二次崩溃,上报调用要包裹。
经验上,错误边界解决"不白屏",全局监听解决"白屏了能知道",探针解决"没崩但没出来",三层合起来才算完整的白屏防护。只做任一层,都还有用户看得见、你却看不见的空白。
1 view
0 likes
0 comments