constructor 执行了两次?- 浅淡 React StrictMode

https://juejin.im/post/5e64d3eff265da57671bd080html


前言

StrictModeReact16.3 版本新增的一个组件,按照官方的说法:react

StrictMode 是一个用来突出显示应用程序中潜在问题的工具。与 Fragment 同样,StrictMode 不会渲染任何可见的 UI。它为其后代元素触发额外的检查和警告。git

我相信不少人和我同样都知道 StrictMode 相似 JavaScript 中的 use strict, 可让 React 程序在更严格的条件下运行,也已经在项目中使用了它,可是仍是不太了解 StrictMode 怎样检测出程序的问题。下面就经过一个问题抛砖引玉,给你们简单的介绍一下 StrictModegithub

问题

你们能够先看一下上面的这段简单的代码,思考一下最终在页面上展现的值也就是 App 组件里 state.id 的值会是多少?

我相信你们对 React 有了解的话就会不假思索地说出 state.id = 1 这个答案,这个答案在生产环境中确实是正确答案,毕竟在 React 的官方文档中告诉了咱们 Class Componentconstructor 只会在渲染时执行一次。可是你在开发环境中运行的时候,显示的答案并不如咱们所想,来一块儿康康 运行的结果 吧: 数组

一个大大的 2 出如今了咱们的屏幕上。为何会是 2,不是说好的 constructor 只会执行一次吗?难道 React 骗了咱们?

这个问题出现的“罪魁祸首”就是 React.StrictMode,它在开发环境中将 constructor 函数调用了两次,至于为何调用两次?其实为了检测意外的反作用,经过调用两次的方式将一些隐藏地比较深的反作用放大,让开发者更好的发现它。详情的内容能够查看下面的介绍。浏览器

StrictMode 功能介绍

一、检测意外的反作用

从概念上讲,React 分两个阶段工做:安全

渲染 阶段会肯定须要进行哪些更改,好比 DOM。在此阶段,React 调用 render,而后将结果与上次渲染的结果进行比较。 提交 阶段发生在当 React 应用变化时。(对于 React DOM 来讲,会发生在 React 插入,更新及删除 DOM 节点的时候。)在此阶段,React 还会调用 componentDidMountcomponentDidUpdate 之类的生命周期方法。 提交阶段一般会很快,但渲染过程可能很慢。所以,即将推出的 concurrent 模式 (默认状况下未启用) 将渲染工做分解为多个部分,对任务进行暂停和恢复操做以免阻塞浏览器。这意味着 React 能够在提交以前屡次调用渲染阶段生命周期的方法,或者在不提交的状况下调用它们(因为出现错误或更高优先级的任务使其中断)。bash

渲染阶段的生命周期包括如下 class 组件方法:async

- constructor
- componentWillMount (or UNSAFE_componentWillMount)
- componentWillReceiveProps (or UNSAFE_componentWillReceiveProps)
- componentWillUpdate (or UNSAFE_componentWillUpdate)
- getDerivedStateFromProps
- shouldComponentUpdate
- render
- setState 更新函数(第一个参数)
复制代码

由于上述方法可能会被屡次调用,因此不要在它们内部编写反作用相关的代码,这点很是重要。忽略此规则可能会致使各类问题的产生,包括内存泄漏和或出现无效的应用程序状态。不幸的是,这些问题很难被发现,由于它们一般具备非肯定性。函数

严格模式不能自动检测到你的反作用,但它能够帮助你发现它们,使它们更具肯定性。经过在开发环境下故意重复调用如下方法来实现的该操做:

- class 组件的 constructor 方法
- render 方法
- setState 更新函数 (第一个参数)
- 静态的 getDerivedStateFromProps 生命周期方法
复制代码

以上这段话全都来自于 React官方中文文档,由于文档上已经对于 StrictMode 是为何要检测意外的反作用以及怎么检测意外的反作用介绍得实在太详细了,笔者以为没啥好补充的了,就直接搬运了过来😂。

render 方法为例,简单看下 StrictMode 关于检测意外的反作用的实现:

真的就像文档介绍的那么简单,仅仅只是对方法调用了两次,没有任何的比较之类的动做。

二、识别不安全的生命周期

React16.3 版本中将一些生命周期方法列为了避免安全的生命周期。至于为何这些生命周期方法是不安全的,能够参考这篇博客的开头,主要仍是考虑到了使用这些生命周期的代码在 React 的将来版本中更有可能出现 bug。

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

StrictMode 就能够帮助咱们检测代码中是否有使用到这些不安全的生命周期方法。在 Class Component 实例化完成后,会去组件实例上寻找有没有 componentWillMountUNSAFE_componentWillMount 这些不安全的生命周期的方法,有就 push 到一个数组里,最后统一在控制台发出警告️。代码实现比较简单,感兴趣能够看下ReactStrictModeWarnings.js 中的 recordUnsafeLifecycleWarningsflushPendingUnsafeLifecycleWarnings 这两个方法。

三、对于使用废弃的 findDOMNode 方法的警告

React 支持用 findDOMNode 来在给定 class 实例的状况下在树中搜索 DOM 节点。一般你不须要这样作,由于你能够将 ref 直接绑定到 DOM 节点。

findDOMNode 也可用于 class 组件,但它违反了抽象原则,它使得父组件须要单独渲染子组件。它会产生重构危险,你不能更改组件的实现细节,由于父组件可能正在访问它的 DOM 节点。findDOMNode 只返回第一个子节点,可是使用 Fragments,组件能够渲染多个 DOM 节点。findDOMNode 是一个只读一次的 API。调用该方法只会返回第一次查询的结果。若是子组件渲染了不一样的节点,则没法跟踪此更改。所以,findDOMNode 仅在组件返回单个且不可变的 DOM 节点时才有效。

这段话也来自官方文档,由于findDOMNode 的这些问题,因此React 决定在 StrictMode 中废弃它,在调用 findDOMNode 会去判断是否在 StrictMode 模式下,有则在控制台打印出警告。

四、检测过期的 context API

由于旧的 Context API 在 context 的值有更新时,没办法保证全部子节点必定能更新(由于中间父组件的 shouldComponentUpdate 返回 false,那么使用到该值的后代组件不会进行更新)的问题,因此 React 在 16.3 版本提出了新的 Context API,因此在 StrictMode 中会检测应用中是否使用到了过期的 Context API

因为老的 Context API 会在 Context 提供者上绑定 childContextTypesgetChildContext 以及在 在 Context 的使用者上绑定用来访问 context 的 contextTypes 属性,因此 StrictMode 只要在组件实例化完成后判断实例上有没有这几个属性就能判断是否使用了老的 Context API,而后做出统一的警告。

五、对于使用字符串 ref API 的警告

这部份内容虽然官方文档上有提到,可是通过笔者的实验,并不能在 StrictMode 下对使用了 string ref API 的行为在控制台产生警告,因此就不在这里多作说起。

写在最后

StrictMode 确实能够帮助咱们让 React 程序运行地更好,更健壮,这个毋庸置疑。可是笔者认为在检测意外的反作用这一点上 React 作的对开发者不够友好吧,虽然对于一部分方法调用两次能够更容易发现出意外的反作用,可是对于刚接触 React 的人或者对 StrictMode 了解不够的人来讲,在开发的时候更可能会认为是 React 出了问题或者本身的写法有问题,致使了重复调用,浪费没必要要的 debug 时间。这一方面可能须要作出更友好的提示。

相关文章
相关标签/搜索