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 時間。這一方面可能須要作出更友好的提示。

相關文章
相關標籤/搜索