React 白屏机制原理分析[共1500字,阅读时长8min]

白屏是什么

白屏是React主动防御机制,React团队认为,显示错误的UI比不显示任何内容更糟糕(例如,转账金额显示错误),因此React遇到无法处理的异常,会选择卸载掉整个组件树。对比着看,原生直接操作DOM的方式通常不会造成白屏的现象,而是会停留在错误发生前的状态。

React渲染机制

白屏的出现与React渲染机制息息相关,白屏实际就是:React渲染过程中发生了未捕获错误,React内部错误处理机制,捕获了该错误,然后进行组件卸载,防止显示错误UI。下图是React的渲染流程:

主要可以分为两个阶段:

Render阶段

根据 JSX转换的ReactElement节点 +旧的Fiber节点树 构建出新的Fiber节点树(Fiber可以被看作为是虚拟DOM)。

Commit阶段

根据Render阶段得出的新的Fiber树更新DOM。

渲染触发时机

  1. 组件初始化。
  2. 通过setState触发组件状态更新。

组件初始化状态变更都会走这套渲染流程,只不过会根据Mount阶段/Update阶段的不同,一些处理逻辑不同,整体流程都是一样的。

白屏机制

Render阶段

在源码中,可以把workLoopSync 函数看作是React的Render 阶段,workLoopSync是被try/catch包裹的。一旦workLoopSync过程中遇到不可处理且未捕获的错误 ,如a是undefined,代码中读取a.b.c,这会导致空指针错误,并且这个错误会被try/catch所捕获,进入handleError 函数中。在handleError函数中会触发React的白屏逻辑和Errorboundary逻辑。

总结:当 Render 阶段(如 workLoopSync 构建 Fiber 树)发生未捕获的 JavaScript 错误时,React 会利用内部的 try/catch 捕获异常。为了防止渲染出数据不一致或损坏的 UI,React 会选择卸载整个组件树(即白屏)。

commit阶段

虽然Render 阶段调用的是handleErrorCommit 阶段调用的是captureCommitPhaseError,但是两个函数在处理白屏逻辑是相似的,可以简单认为是一样的。

commit阶段的流程包括:更新DOM,执行useLayoutEffect,执行useEffect。这三部分都被React内置的try/catch 包裹,异常处理函数是captureCommitPhaseError 。如果Commit阶段,代码抛出了未捕获的错误,仍然会触发captureCommitPhaseError函数。

白屏例子

第一个例子

最典型的白屏例子:

javascript 复制代码
import React, { useState } from 'react';

export default function Page404() {
  const [data, setData] = useState(null)
  return <div className="fx-center mt-80">{data.message}</div>;
}

上述代码的执行流程是:

  1. 编译时 :JSX 转变为React.createElement/_jsx,创建ReactElement的函数语句.
  2. 运行时 :组件 Render 过程中, _jsx 函数读取参数,因 data.message 求值失败抛出 TypeError。React 捕获这个错误后,触发了全量的 Unmount 保护机制 ,导致白屏。如果存在ErrorBoundary组件,则会使用Error Boundary兜底。
第二个例子
javascript 复制代码
export default function Page404() {
  const [data, setData] = useState(null)

  console.log(data.a.b)

  return <div className="fx-center mt-80">{data || null}</div>;
}

这段代码同样也会发生白屏,本质上, 函数组件的函数体执行本身 就是 Render 阶段的一部分 。React 在构建 Fiber 树时会直接调用该组件函数。因此, console.log(data.a.b) 抛出的空指针异常,会直接被 React 内部的try/catch捕获,进入handleError 函数,React 卸载整个组件树,从而导致白屏

第二个例子改进
javascript 复制代码
import React, { useState } from 'react';

export default function Page404() {
  const [data, setData] = useState(null)

  try {
    console.log(data.a.b)
  } catch (error) {

  }

  return <div className="fx-center mt-80">{data || '404'}</div>;
}

console.log(data.a.b) 虽然发生了错误,但因为使用了 try/catch ,这个错误没有抛出给 React内部的try/catch,所以 React 并不知情。

第三个例子(这个例子很重要)

点击按钮的时候,是否会发生白屏错误?

javascript 复制代码
import React, { useState } from 'react';

export default function Page404() {
  const [data, setData] = useState(null)

  const handleClick = () => {
    console.log(data.a.b.c)
  }

  return <div className="fx-center mt-80"><button onClick={handleClick}>404</button><h1>{data || '404'}</h1></div>;
}

答案是:不会

当用户点击按钮时, Render 阶段早已结束 ,Commit 阶段也结束了,UI 已经稳定地显示在屏幕上。之前我们说过Render过程的未捕获错误,才会被React内部的try/catch所捕获。

总结: 这个例子表明不是发生了JS错误,就会白屏。白屏对发生时机有着严格要求,必须是渲染过程中,项目才会白屏。

第四个例子

点击按钮的时候,是否会发生白屏错误?

javascript 复制代码
import React, { useState } from 'react';

export default function Page404() {
  const [data, setData] = useState({message:"404"})

  const handleClick = () => {
    setData(null)
  }

  return <div className="fx-center mt-80"><button onClick={handleClick}>404</button><h1>{data.message}</h1></div>;
}

答案是:会 当用户点击按钮时,调用handleClick函数,执行 setData(null) 修改状态,这会触发当前组件重新渲染。在Render过程中,因为data被更新为null,代码执行到 《h1》 {data.message}《/h1》(掘金用h1标签就自动变大了,因此用《》代替<>),因为data是null,代码会抛出空指针报错,这会被React内部的try/catch所捕获,从而触发白屏逻辑。

第五个例子
javascript 复制代码
import React, { useEffect, useState } from 'react';

export default function Page404() {
  const [data, setData] = useState(null)

  useEffect(() => {
    console.log(data.a.b.c)
  }, [])

  return <div className="fx-center mt-80">404</div>;
}

答案是:会白屏 虽然 useEffect 是异步执行的,但它是由 React Scheduler(调度器) 接管的。当 Commit 阶段完成后,Scheduler 会调度并执行 flushPassiveEffects 方法来处理所有副作用。

在此过程中,React 会显式地用 try-catch 包裹回调函数的执行。因此, data.a.b.c 抛出的空指针异常会被 React 精确捕获,并触发 captureCommitPhaseError 流程。

总结

白屏是组件渲染过程中抛出未捕获的错误,被React内部的错误处理机制捕获,从而触发的一种主动防御的机制。

相关推荐
技术狂人16812 分钟前
(六)大模型算法与优化 15 题!量化 / 剪枝 / 幻觉缓解,面试说清性能提升逻辑(深度篇)
人工智能·深度学习·算法·面试·职场和发展
vx_bisheyuange17 分钟前
基于SpringBoot的青年公寓服务平台
前端·vue.js·spring boot·毕业设计
web前端12317 分钟前
前端如何开发一个MCP Server - 安全审计实战项目介绍
前端·mcp
奶糖 肥晨24 分钟前
JS自动检测用户国家并显示电话前缀教程|vue uniapp react可用
javascript·vue.js·uni-app
Dr_哈哈36 分钟前
Node.js fs 与 path 完全指南
前端
啊花是条龙41 分钟前
《产品经理说“Tool 分组要一条会渐变的彩虹轴,还要能 zoom!”——我 3 步把它拆成 1024 个像素》
前端·javascript·echarts
C_心欲无痕42 分钟前
css - 使用@media print:打印完美网页
前端·css
技术狂人1681 小时前
(七)大模型工程落地与部署 10 题!vLLM/QPS 优化 / 高可用,面试实战必备(工程篇)
人工智能·深度学习·面试·职场和发展·vllm
青茶3601 小时前
【js教程】如何用jq的js方法获取url链接上的参数值?
开发语言·前端·javascript
脩衜者1 小时前
极其灵活且敏捷的WPF组态控件ConPipe 2026
前端·物联网·ui·wpf