破题

第 3 讲,有说到 错误边界 内容:若渲染异常,在无保护措施下,页面白屏。
进阶打怪:会产生什么影响、会造成什么后果 —-> 怎么做、解决方案。
所以这类问题的中心会发生偏移:从 是什么 到 怎么做,即从 解释原理 到 提供方案。
如何评判方案的优劣呢?从三个方面:

1. 解决问题

表达方式上,展现自己解决问题能力,如回答:我在 X 项目中遇到 Y 问题,通过排查 a、b、c 三点后找到问题根源,最后改动项目中 d、e、f 点完成上线修复。

2. 预防问题

表达方式上,展现自己 问题处理 以 预防为主。
讲项目初始化时,做的几条措施预防同类低级错误。

3. 工程化方案

以上两种方案停留在个人层面,缺乏全盘考虑。
工程化通常以标准化为抓手完成降本增效,它涉及到的方向有:

  1. 模块标准化;
  2. 流程标准化;
  3. 代码风格标准化;
  4. 。。。

通过标准化的方案解决同类型问题。解决过程减少任务因素,引入 自动化 过程,如:代码风格,引入 ESLint 工具。
除了解决自己所遇到的问题,还需有团队贡献,具备团队视野。(自己欠缺)
如何量化成果???项目价值,业绩贡献,都需落到数据支撑。修改 BUG 后,需提供具有参考性的指标。

审题

此题的回答方向:

  1. 是什么,即阐述现象、原理,在说原理时,需提供相符案例;
  2. 怎么解决,即从工程化角度,拿出通用方案,并量化结果,给出数据和指标。

面试 - 12- React 渲染异常后果 - 图1

是什么

提供出案例。
开发模式下:报错,react-error-overlay;
生成模式下:页面白屏。

官方对现象的解释:组件内的 JavaScript 错误会导致 React 的内部状态被破坏,并且在下一次渲染时产生可能无法追踪的错误。这些错误基本上是由较早的其他代码(非 React 组件代码)错误引起的,但 React 并没有提供一种在组件中优雅处理这些错误的方式,也无法从错误中恢复。

大白话就是,若有 JS 的错误出现在 React 内部渲染层,就会导致整个应用崩溃。现象:将整个界面移除,出现白屏。

怎么做

通用方案

第 3 讲,错误边界 概念,用到了getDerivedStateFromErrorcomponentDidCatch两个生命周期函数。用这两种方案来处理,缺乏整体性考虑。
通用方案应该是:从预防、兜底两个角度着手。

预防

预防需知病根在那儿。
分析渲染异常出现的原因:在渲染层,即 render 中 return 后的 JSX,都是在进行数据的 拼装、转换。

  • 若在拼装出错,直接导致编译失败;
  • 若在转换出错,不易被发现;

前端的渲染数据大多来自后端接口,数据可靠性就是一个非常重要的问题。此问题被称为:null-safety,空安全。无最优解。
目前解决方案:

  • idx (需配置 Babel 插件,社区使用者少);
  • ES2020 中的 Optional Chaining(可选操作链符),如:xxx?.yyyy ;
    浏览器兼容性,需配置 Babel:"plugins": ["@babel/plugin-proposal-optional-chaining"]
  • TS v3.7 可直接使用 可选操作链符,无需配置 Babel;

    兜底

    空安全 的预防方案都足够完善,但还需一个兜底方案,以防万一。
    兜底应限制崩溃的层级。错误边界加到哪里,崩溃到此为止,其他组件可正常使用,所以只需给 UI 组件添加错误边界,可用第 5 章节中提到的高阶组件:

    1. export const errorBoundary = WrapperComponent => {
    2. return class Wrap extends Component {
    3. state = { hasError: false }
    4. static getDerivedStateFromError(error) {
    5. return {
    6. hasError: true,
    7. error
    8. }
    9. }
    10. componentDidCatch(error: Error, info: React.ErrorInfo) {
    11. // 上报错误。。。
    12. }
    13. render() {
    14. return (
    15. this.state.hasError ? <ErrorDefaultUI error={this.state.error} /> : <WrapperComponent />
    16. )
    17. }
    18. }
    19. }
    1. @errorBoundary
    2. export default class XXX{}

    重点:此方案可抽取为一个 NPM 包,提供复用能力。

    量化结果

    评估做得好不好?用数据说话。

  1. 预防层面,需看空安全方案在项目中的覆盖量,保证团队内项目都将空安全用起来;
  2. 兜底层面,需看保障方案在项目中的覆盖率,统计兜底页面成功兜底的次数,最后兜底页面展示时,能够及时完成线上报警。

量化工作主要两个方面:覆盖、统计。
覆盖:最简单的方法就是查看package.json是否引入相关库。如:有的公司代码检测使用统一的工具(sonar,那只需配置文件即可)。若公司未接入,则用最原始方案,写个 Node 脚本,拉取相关仓库代码自行分析,每周产出一个 dashboard 查看使用情况。

答题

React 异常渲染时,若无任何拦截情况下,会出现白屏现象。其原因:在渲染层出现 JS 错误,导致整个应用崩溃。通常这种错误是在 render 中没有控制好空安全,取到空值导致的。
解决方案上,从两个方面入手:预防、兜底。
预防策略上,空安全解决方法,有三个方案:

  1. 引入外部函数,如:Facebook 的 idx,lodash.get;
  2. 引入 Babel 插件,使用 ES2020的标准 - 可选链操作符(推荐);
  3. TS v3.7 版本后,可直接使用 可选链操作符。

兜底策略上,考虑团队内部,则抽取兜底公共高阶组件,封装,发布 NPM 供团队使用;
量化:从最终数据来看,预防和兜底覆盖了团队 100% 的 React 项目,前几个月兜底组件统计到日均 X 次报警信息,其中 xx% 是关键业务。经分析统计,关键的 UI 组件添加兜底组件进行拦截,然后内部培训,对易错点代码指导,走查代码。后续到现在,线上只收到 x 次报警。
面试 - 12- React 渲染异常后果 - 图2