React Hook 實踐小結

hello~親愛的觀衆老爺們你們好~最近負責重構某個內部系統,既然是內部系統,那固然能夠盡情搞事情,分析需求後決定採用 React 最新版本進行重構。既然是最新的版本,那固然是使用 Hooks 進行開發了。開發的過程並不是一路順風,但也算是踩過很多坑也從新爬出來了,小結後有了這篇文章~javascript

注意~這篇文章是實踐相關的討論,若是不太清楚 Hooks 的同窗,可能這篇文章不太適合你。同時,我在實踐的過程當中,使用了若干 React 官方不建議的模式,本文主要是對此進行討論,但願對你編寫基於 HooksReact 代碼時有所幫助~如下是正文:html

在循環,條件中調用 Hook

React 的官網在 Hook 規則 一節中,有這麼一段話:前端

不要在循環,條件或嵌套函數中調用 Hook, 確保老是在你的 React 函數的最頂層調用他們。java

然而,我在實際的開發過程當中,(爲了省事)常常違背了這條原則。先理解一下爲什麼官方不建議在循環,條件或嵌套函數中調用 Hook。函數組件內的 Hook 是基於鏈表進行註冊的,也就是一個有着固定順序的序列,以下所示:react

Hook1 ⟶️ Hook2 ⟶️ Hook3 ...⟶️... HookN
複製代碼

假設 Hook2 處於判斷條件之中,一旦 condition 修改,執行的順序就會發生改變:數組

if (condition) {
  useHook2();
}

執行順序變爲:Hook1 ⟶️ Hook3 ...⟶️... HookN
複製代碼

順序會發生偏移,從而致使 React 內部報錯。緩存

大體瞭解代碼原理後,就能理解爲什麼官方不建議在循環或條件中使用 Hook。然而,這個問題的根本緣由是順序產生了變化,於是致使 bug 的出現。那若是咱們能保證執行順序呢?好比:在本地開發環境中須要增長一些調試代碼,但不但願線上出現對應的代碼,通常咱們會這麼寫:服務器

if (process.env.NODE_ENV === 'development') {
  //do sth...
}
複製代碼

發佈正式代碼時,這段調試代碼會被打包工具正確去除。因爲環境變量是固定的(同一環境之中基本上不會產生變化),於是即便 Hook 處於條件判斷之中,函數中 Hook 的順序是固定的,使用是並不會產生問題:函數

function Test() {
  let test;
  let setTest;
  if (process.env.NODE_ENV === 'development') { 
    [test, setTest] = useState(1);  // eslint-disable-line
  }                                           

  // 添加 eslint-disable-line,是由於 ESLint 在開發環境中會檢測 `Hook` 是否在合理的上下文之中(即不在條件判斷或循環之中),否則會報錯並中止渲染

  return (
    <div> <p>{test}</p> <button onClick={() => setTest(test + 1)}>click</button> </div>
  )
}
複製代碼

在循環條件中同理(只要循環次數固定,也不會有問題)。只要肯定 Hook 的順序不變,何嘗不可在條件或循環中使用,只要你清楚知道本身在幹什麼~工具

依賴欺騙與精確依賴

不管是 React 的文檔仍是 Hooks 相關的文章,不少都建議咱們對 Hook 的依賴數組誠實,如實填寫 Hook 內的變量,以免 bug 的產生:

useEffect(() => {
    reportToService(userName, userId, ...)
}, [userName, userId, ...])
複製代碼

然而,這並不應是死板的教條~想象一下上述代碼的場景,這是一段上報數據到服務器的邏輯。若是用戶登出後,用戶名之類的信息必然產生變化,那麼 useEffect 會從新執行,假設這是一段統計 PV 的代碼,那就存在重複統計的問題。於是,只要你清楚知道本身在幹什麼,依賴項實際上是能夠根據實際狀況填寫的,不必強行將所有用到的變量填進去。

而精確依賴,就是 React 不可變數據的一個體現,某程度上說,應該說是避免錯誤填寫依賴,考慮如下例子:

function Test() {
  const [test, setTest] = useState({
    key1: 1,
    key2: 2
  });

  useEffect(() => {
    console.log(test.key2);
  }, [test]);

  return (
    <div> <p>{test.key1}</p> <button onClick={() => setTest(test => { return { ...test, key1: test.key1 + 1 } })}> click </button> </div>
  )
}
複製代碼

以上例子能夠正常運行,ESLint 也不會報錯。然而當咱們不斷點擊按鈕時,useEffect 會不斷執行,打印出 test.key2 的值。這是因爲按鈕點擊後, test 是一個新的對象,於是 useEffect 的依賴項發生了變化,因而不斷被執行。但這是毫無心義的,咱們真正想監聽的的是 test.key2,於是依賴的項應該精準地填寫爲 test.key2,修改後 useEffect 只會在 test.key2 變化後纔會執行。

精確依賴是一個很小的細節,但常常會致使重複執行 Hook,在定位相似問題時不妨先檢查一下依賴了錯誤的變量。

Hook 既不是 setState,也不是生命週期函數

因爲我很長一段時間沒寫 React,於是一開始寫 Hook 的時候仍是帶着濃重的 class 色彩,基本能夠說只是將 class 「翻譯」成 function 而已。

在使用 useState 的時候,往裏面丟一個很大的對象,模仿以前 state 的寫法,但這是典型的反模式。儘管數據的封裝是必須的,如將用戶相關的數據統合在 userInfo 對象之中,然而將所有內容像以前的 class 同樣,放在一個 state 中,是不可取的,會致使這個 Hook 變得至關繁重,也不利於邏輯複用,違背了 Hook 最初的目的。

Hook 不是 setState 仍是比較好理解,但 Hook 不是生命週期函數就不是那麼好習慣了。例如咱們常常在 componentWillUnmount 中解除定時器、解綁事件等等:

componentWillUnmount() {
   clearTimeout(timer);
   removeEventListener('click', handler);
   ...
}
複製代碼

然而,在 Hook 中,把解除定時器、解綁事件等操做所有寫到一個 useEffect 中去,並非最佳實踐。經過上文咱們瞭解到,儘可能要作到精確依賴,避免沒必要要的開銷。而從邏輯複用的角度而言,將代碼按照功能拆開,更有利於複用。於是咱們應該寫成:

useEffect(() => {
   const timer = setTimeout(() => {
       ...
   });
   
   return () => {
     clearTimeout(timer);
   }
}, []);

useEffect(() => {
   element.addEventListener('click', handler);
   
   return () => {
     element.removeEventListener('click', handler);
   }
}, []);

...
複製代碼

class 中 每一個生命週期函數只有一個,而 function 中相應的 Hooks 能夠有多個~分拆有利於代碼清晰與邏輯複用。

小議性能

性能是個很大的話題,在 Hooks 中性能相關的問題,基本能夠獨立一篇文章來寫了~因爲 React 遵循的是不可變數據,所以它的更新是批量的:

(某個節點狀態發生改變,它以及它的子孫組件都須要從新計算 Virtual DOM)

按照最佳實踐,咱們應該使用 useMemouseCallback 等等進行緩存,避免重複生成變量而致使無心義的重算 Virtual DOM。但喜歡「搗亂」的我,稍微提出一點反模式以作拋磚引玉之用——咱們的應用可能沒那麼大以致於須要全面考慮性能。

使用 Hook 進行開發時,爲了保證變量不變,須要使用不少 useMemouseCallback 之類的鉤子,爲了極致的性能每一個變量與方法都要被這些鉤子包囊。有時候感受就像是不斷地書寫模板代碼,回憶起以前被 Redux 支配的恐懼了麼~ Hook 也有點這個感受。

要明確一點,性能確實很重要。但也要明白,對於通常的前端應用而言,在 1ms 仍是 10ms 內完成 diff,其實意義是不大的。按照經驗而言,低端手機百萬級別內的運算,並不會產生明顯的卡頓。然而 DOM 就至關慢了,低端機大概 6000 個 DOM 從新渲染,就會產生很是明顯的卡頓。於是,就我的的角度而言,保持應用的高性能值得稱道,但不建議在中小的應用中追求極致性能,在碰到性能瓶頸時再進行優化也何嘗不可。

小結

以上就是本文的所有內容啦!這是我在項目中使用 Hook 進行開發後的一點思考,畢竟「盡信書,不如無書」,只有經過實踐才能掌握新的技術。

以上是我的的一點淺見,感謝各位看官大人看到這裏。知易行難,但願本文對你有所幫助~謝謝!

相關文章
相關標籤/搜索