JavaScript

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > JS前端白屏解决

JS前端白屏前世今生及解决方式

作者:包邦东

这篇文章主要为大家介绍了JS前端白屏前世今生及解决方式案例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

前言

白屏(Blank Screen),它无所不及,摧枯拉朽,令用户体感全失、测试提 P0 相见、研发不寒而栗,胆战心惊,只知匆忙回滚。

对于离用户最近的前端,更是重灾区,浏览器上只要出现白屏,先找前端准没错。

近期工作中频频遇到线上白屏事故,我借这个机遇,介绍为什么会产生白屏,以及应对之道。

兵法著:知彼知己,百战不殆;不知彼知己,一胜一负,不知彼不知己,每战必殆。

只有足够了解白屏,了解自身代码的局限性,才能云淡风轻,编程游刃有余。

白屏从何而来

导致白屏的原因,大概率分为两种:

两者虽然“各有千秋”,但从现代前端视角来看,都和 SPA 框架的广泛应用逃不了干系。

资源访问错误

这里的资源特指 JavaScript 脚本、样式表、图片等静态资源,不包括服务调用等动态资源。

最典型的例子莫过于 React、Vue 等 SPA 框架构建的 Web 应用,一旦 [bundle|app].js 因为网络原因访问失败,便会引发页面白屏。

你可以访问 https://vue-ebgcbmiy3-b2d1.vercel.app/,按照如下步骤复现:打开 DevTools > Network,找到 app.3b315b6b.js,右键并选中 Block request URL,随后刷新页面。

代码执行出错

如果说资源访问错误还有回旋的余地,可能用户的网络不稳定,重试几次便能恢复正常。

那么在 render 过程中,代码执行出错无异于被宣判死刑,包括但不限于:

你必须经历本地调试,CI、CD,构建部署等一系列措施、或者直接 rollback.

为什么 read properties of undefined 就白屏了?

先请教一个问题,试问以下代码的执行是否会导致页面白屏?

为了摆脱框架的约束,我特意使用原生 JavaScript、以命令式的编程范式动态渲染一个网页。

<body>
  <div id="root"></div>
  <script>
    const arr = ["webpack", "rollup", "parcel"];
    const root = document.getElementById("root");
    const ul = document.createElement("ul");
    for (let i = 0; i <= arr.length - 1; i++) {
      const li = document.createElement("li");
      li.innerHTML = arr[i];
      ul.appendChild(li);
    }
    root.appendChild(ul);
    const h1 = document.createElement("h1");
    // trigger read properties of undefined
    h1.textContent = document.createTextNode({}.a.b);
    root.appendChild(h1);
  </script>
</body>

浏览器的真实表现是 ul 被正常渲染,而 h1 直接不渲染,两者互不影响,更不会导致白屏。

把视角切回 React,我们将渲染 ul h1 的过程类比为渲染 <Ul /> 组件 和 <H1 /> 组件,看看会发生什么?

const Ul = () => (
  <ul>
    {["webpack", "rollup", "parcel"].map((v) => (
      <li>{v}</li>
    ))}
  </ul>
);
// trigger read properties of undefined
const H1 = () => <h1>{{}.a.b}</h1>;
const App = () => {
  return (
    <>
      <Ul />
      <H1 />
    </>
  );
};
ReactDOM.render(<App />, document.getElementById("root"));

毫无意外,页面呈现白屏状态,<H1 /> 的渲染错误致使整个 <App /> 都崩溃了。

根本原因是自 React 16 起,任何未被错误边界捕获的错误将会导致整个 React 组件树被卸载

翻译一下就是如果在组件的渲染期间内,发生了 Uncaught Errors,而又未被 Error Boundaries 捕获,整个 <App /> 所表示的 DOM 结构都被会移除,如下所示:

ReactDOM.render(null, document.getElementById("root"));

React 用白屏真正诠释了什么叫唇寒齿亡,牵一发而动全身,这也验证了我之前的说法,现代 Web 应用频繁白屏和 SPA 框架逃不了干系。

但你能说这个机制是负向优化的吗?官方说法是:

我们对这一决定有过一些争论,但根据我们的经验,把一个错误的 UI 留在那比完全移除它要更糟糕。例如,在类似 Messenger 的产品中,把一个异常的 UI 展示给用户可能会导致用户将信息错发给别人。同样,对于支付类应用而言,显示错误的金额也比不呈现任何内容更糟糕。

我越来越相信,前端层出不穷的框架或是新技术,虽然它的 leverage 足够大,但背后隐含着 trade-off,在绝大多数场景下表现优异,在另一些场景下你也必须要接受它的“规则”。

为什么不能是 ⬛ 屏、🟦 屏?

既然 DOM 都被移除了,只剩下个光秃秃的 div#app 节点,加上 body 的默认背景颜色是 #FFF,理所应当白屏。

<body>
<div id="app"></div>
<script src="/js/chunk-vendors.61a12961.js"></script>
<script src="/js/app.3b315b6b.js"></script>
</body>

因此,不仅黑屏、蓝屏可以实现,只要将 body 的背景颜色稍作调整,彩虹屏也可以实现,彼时复盘文档的标题名为 「XXX 引发彩虹屏」,活成了前端喜剧人的样子。

我认为白屏只是一种代号,引申的含义是页面无内容渲染。

我还想强调,白屏只是一种外在表现形式,内在错误已经发生,不可挽回,肯定会给用户带来功能上的影响,只不过白屏的视觉冲击力最强,大脑直觉反馈十分严重。

如何降低白屏的“破坏力”

不再赘述如何避免白屏,因为错误时时刻刻会发生,我们能做的是尽人事,遵循以下原则:

我们关注的是错误已经发生的窘境下,如何及时补救,把外在的不良表现弱化成用户可以接受,或者无感知的状态。

借助于 ErrorBoundary,它能捕获任意子组件在渲染期间发生的 Uncaught Errors,从而避免整体组件树的卸载,把白屏扼杀在摇篮中。

除此之外,它还能对渲染错误的组件做兜底,具体的处理措施有两种:熔断和降级。

组件 “熔断”

熔断机制指的是在股票市场的交易时间中,当价格波动的幅度达到某一个限定的目标(熔断点)时,对其暂停交易一段时间的机制。 此机制如同保险丝在电流过大时候熔断,避免引发更大的事故,因此得名。

它被大量应用于容灾体系,对应 React 体系中,熔断点等同于渲染错误发生,暂定交易等同于卸载组件,直接不渲染,舍车保帅。

直接看例子:

import { ErrorBoundary } from "react-error-boundary";
const Other = () => <h1>I AM OTHER</h1>;
const Bug = () => {
  const [val, setVal] = useState({});
  const triggerError = () => {
    setVal(undefined);
  };
  return (
    <>
      <button onClick={triggerError}>trigger render error</button>
      <h1>I HAVE BUG, DO NOT CLICK ME</h1>
      {Object.keys(val)}
    </>
  );
};
const App = () => (
  <>
    <Other />
    <ErrorBoundary fallbackRender={() => null}>
      <Bug />
    </ErrorBoundary>
  </>
);

组件优雅降级

优雅降级指使用 替代渲染出错的组件,并做符合功能场景,用户心智的提示。

import { ErrorBoundary } from "react-error-boundary";
function ErrorFallback({ error, resetErrorBoundary }) {
  return (
    <div role="alert">
      <p>Something went wrong:</p>
      <pre>{error.message}</pre>
      <button onClick={resetErrorBoundary}>Try again</button>
    </div>
  );
}
const App = () => (
  <>
    <Other />
    <ErrorBoundary fallbackRender={ErrorFallback}>
      <Bug />
    </ErrorBoundary>
  </>
);

以上 demo 所选择的错误边界库为 https://github.com/bvaughn/react-error-boundary,可在生产环境中投入使用。

前提是大家都要有对每个组件加上错误边界的共识,配合团队内部的监控上报和 Lint 检测,才能最大限度降低白屏的“破坏力”,打造一个稳定性更强的线上环境。

题外话:主动 throw error 导致白屏

我宁愿犯错,也不愿什么也不做。

这一点我和 React Team 的观点相同,与其展示错误的 UI,不如不展示。

错误的 UI,随时是个定时炸弹,在特定情况下就会爆炸,试想用户在错误的界面进行操作,小则造成 BUG,大则造成经济损失、安全泄露,会带来不可损失的影响,所以遇到对于非预期的行为,一定要主动 throw error,并做好组件熔断及降级。

以上就是JS前端白屏前世今生及解决方式的详细内容,更多关于JS前端白屏解决的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文