React Hooks究竟是什麼呢?

摘要: React Hooks原理解析。

我們大部分 React 類組件可以保存狀態,而函數組件不能? 並且類組件具有生命週期,而函數組件卻不能?

React 早期版本,類組件可以通過繼承PureComponent來優化一些不必要的渲染,相對於函數組件,React 官網沒有提供對應的方法來緩存函數組件以減少一些不必要的渲染,直接 16.6 出來的 React.memo函數。

React 16.8 新出來的Hook可以讓React 函數組件具有狀態,並提供類似 componentDidMountcomponentDidUpdate等生命週期方法。

類被會替代嗎?

Hooks不會替換類,它們只是一個你可以使用的新工具。React 團隊表示他們沒有計劃在React中棄用類,所以如果你想繼續使用它們,可以繼續用。

我能體會那種總有新東西要學的感覺有多痛苦,不會就感覺咱們總是落後一樣。Hooks 可以當作一個很好的新特性來使用。當然沒有必要用 Hook 來重構原來的代碼, React團隊也建議不要這樣做。

Go Go

來看看Hooks的例子,咱們先從最熟悉的開始:函數組件。

以下 OneTimeButton 是函數組件,所做的事情就是當我們點擊的時候調用 sayHi 方法。

    import React from 'react';
    import { render } from 'react-dom';
    
    function OneTimeButton(props) {
      return (
        <button onClick={props.onClick}>
            點我點我
        </button>
      )
    }
    
    function sayHi() {
      console.log('yo')
    }
    
    render(
      <OneTimeButton onClick={sayHi}/>,
      document.querySelector('#root')
    )

我們想讓這個組件做的是,跟蹤它是否被點擊,如果被點擊了,禁用按鈕,就像一次性開關一樣。

但它需要一個state,因爲是一個函數,它不可能有狀態(React 16.8之前),所以需要重構成類。

函數組件轉換爲類組件的過程中大概有5個階段:

  • 否認:也許它不需要是一個類,我們可以把 state 放到其它地方。
  • 實現: 廢話,必須把它變成一個class,不是嗎?
  • 接受:好吧,我會改的。
  • 努力加班重寫:首先 寫 class Thing extends React.Component,然後 實現 render等等 。
  • 最後:添加state。


    class OneTimeButton extends React.Component {
      state = {
        clicked: false
      }
    
      handleClick = () => {
        this.props.onClick();
    
        // Ok, no more clicking.
        this.setState({ clicked: true });
      }
    
      render() {
        return (
          <button
            onClick={this.handleClick}
            disabled={this.state.clicked}
          >
            You Can Only Click Me Once
          </button>
        );
      }
    }

這是相當多的代碼,組件的結構也發生了很大的變化, 我們需要多個小的功能,就需要改寫很多。

使用 Hook 輕鬆添加 State

接下來,使用新的 useState hook向普通函數組件添加狀態:

    import React, { useState } from 'react'
    
    function OneTimeButton(props) {
      const [clicked, setClicked] = useState(false)
      
      function doClick() {
        props.onClick();
        setClicked(true)
      }
    
      return (
        <button
          onClick={clicked ? undefined : doClick}
          disabled={clicked}
        >
          點我點我
        </button>
      )
    }

這段代碼是如何工作的

這段代碼的大部分看起來像我們一分鐘前寫的普通函數組件,除了useState

useState是一個hook。 它的名字以“use”開頭(這是Hooks的規則之一 - 它們的名字必須以“use”開頭)。

useState hook 的參數是 state 的初始值,返回一個包含兩個元素的數組:當前state和一個用於更改state 的函數。

類組件有一個大的state對象,一個函數this.setState一次改變整個state對象。

函數組件根本沒有狀態,但useState hook允許我們在需要時添加很小的狀態塊。 因此,如果只需要一個布爾值,我們就可以創建一些狀態來保存它。

由於Hook以某種特殊方式創建這些狀態,並且在函數組件內也沒有像setState函數來更改狀態,因此 Hook 需要一個函數來更新每個狀態。 所以 useState 返回是一對對應關係:一個值,一個更新該值函數。 當然,值可以是任何東西 - 任何JS類型 - 數字,布爾值,對象,數組等。

現在,你應該有很多疑問,如:

  • 當組件重新渲染時,每次都不會重新創建新的狀態嗎? React如何知道舊狀態是什麼?
  • 爲什麼hook 名稱必須以“use”開頭? 這看起來很可疑。
  • 如果這是一個命名規則,那是否意味着我可以自定義 Hook。
  • 如何存儲更復雜的狀態,很多場景不單單隻有一個狀態值這麼簡單。

Hooks 的魔力

將有狀態信息存儲在看似無狀態的函數組件中,這是一個奇怪的悖論。這是第一個關於鉤子的問題,咱們必須弄清楚它們是如何工作的。

原作者得的第一個猜測是某種編譯器的在背後操衆。搜索代碼useWhatever並以某種方式用有狀態邏輯替換它。

然後再聽說了調用順序規則(它們每次必須以相同的順序調用),這讓我更加困惑。這就是它的工作原理。

React第一次渲染函數組件時,它同時會創建一個對象與之共存,該對象是該組件實例的定製對象,而不是全局對象。只要組件存在於DOM中,這個組件的對象就會一直存在。

使用該對象,React可以跟蹤屬於組件的各種元數據位。

請記住,React組件甚至函數組件都從未進行過自渲染。它們不直接返回HTML。組件依賴於React在適當的時候調用它們,它們返回的對象結構React可以轉換爲DOM節點。

React有能力在調用每個組件之前做一些設置,這就是它設置這個狀態的時候。

其中做的一件事設置 Hooks 數組。 它開始是空的, 每次調用一個hook時,React 都會向該數組添加該 hook

爲什麼順序很重要

假設咱們有以下這個組件:

    function AudioPlayer() {
      const [volume, setVolume] = useState(80);
      const [position, setPosition] = useState(0);
      const [isPlaying, setPlaying] = useState(false);
    
      .....
    }

因爲它調用useState 3次,React 會在第一次渲染時將這三個 hook 放入 Hooks 數組中。

下次渲染時,同樣的3hooks以相同的順序被調用,所以React可以查看它的數組,並發現已經在位置0有一個useState hook ,所以React不會創建一個新狀態,而是返回現有狀態。

這就是React能夠在多個函數調用中創建和維護狀態的方式,即使變量本身每次都超出作用域。

多個useState 調用示例

讓咱們更詳細地看看這是如何實現的,第一次渲染:

  1. React 創建組件時,它還沒有調用函數。React 創建元數據對象和Hooks的空數組。假設這個對象有一個名爲nextHook的屬性,它被放到索引爲0的位置上,運行的第一個hook將佔用位置0

  2. React 調用你的組件(這意味着它知道存儲hooks的元數據對象)。

  3. 調用useState,React創建一個新的狀態,將它放在hooks數組的第0位,並返回[volume,setVolume]對,並將volume 設置爲其初始值80,它還將nextHook索引遞增1。

  4. 再次調用useState,React查看數組的第1位,看到它是空的,並創建一個新的狀態。 然後它將nextHook索引遞增爲2,並返回[position,setPosition]

  5. 第三次調用useState。 React看到位置2爲空,同樣創建新狀態,將nextHook遞增到3,並返回[isPlaying,setPlaying]

現在,hooks 數組中有3個hook,渲染完成。 下一次渲染會發生什麼?

  1. React需要重新渲染組件, 由於 React 之前已經看過這個組件,它已經有了元數據關聯。
  2. ReactnextHook索引重置爲0,並調用組件。
  3. 調用useState,React查看索引0處的hooks數組,並發現它已經在該槽中有一個hook。,所以無需重新創建一個,它將nextHook推進到索引1並返回[volume,setVolume],其中volume仍設置爲80
  4. 再次調用useState。 這次,nextHook1,所以React檢查數組的索引1。同樣,hook 已經存在,所以它遞增nextHook並返回[position,setPosition]
  5. 第三次調用useState,我想你知道現在發生了什麼。

就是這樣了,知道了原理,看起來也就不那麼神奇了, 但它確實依賴於一些規則,所以纔有使用 Hooks 規則。

Hooks 的規則

自定義 hooks 函數只需要遵守規則 3 :它們的名稱必須以“use”爲前綴。

例如,我們可以從AudioPlayer組件中將3個狀態提取到自己的自定義鉤子中:

    function AudioPlayer() {
      // Extract these 3 pieces of state:
      const [volume, setVolume] = useState(80);
      const [position, setPosition] = useState(0);
      const [isPlaying, setPlaying] = useState(false);
    
      // < beautiful audio player goes here >
    }

因此,咱們可以創建一個專門處理這些狀態的新函數,並使用一些額外的方法返回一個對象,以便更容易啓動和停止播放,例如:

    function usePlayerState(lengthOfClip) {
      const [volume, setVolume] = useState(80);
      const [position, setPosition] = useState(0);
      const [isPlaying, setPlaying] = useState(false);
    
      const stop = () => {
        setPlaying(false);
        setPosition(0);
      }
    
      const start = () => {
        setPlaying(true);
      }
    
      return {
        volume,
        position,
        isPlaying,
        setVolume,
        setPosition,
        start,
        stop
      };
    }

像這樣提取狀態的一個好處是可以將相關的邏輯和行爲組合在一起。可以提取一組狀態和相關事件處理程序以及其他更新邏輯,這不僅可以清理組件代碼,還可以使這些邏輯和行爲可重用。

另外,通過在自定義hooks中調用自定義hooks,可以將hooks組合在一起。hooks只是函數,當然,函數可以調用其他函數。

總結

Hooks 提供了一種新的方式來處理React中的問題,其中的思想是很有意思且新奇的。

React團隊整合了一組很棒的文檔和一個常見問題解答,從是否需要重寫所有的類組件到鉤Hooks是否因爲在渲染中創建函數而變慢? 以及兩者之間的所有東西,所以一定要看看。

代碼部署後可能存在的BUG沒法實時知道,事後爲了解決這些BUG,花了大量的時間進行log 調試,這邊順便給大家推薦一個好用的BUG監控工具 Fundebug

原文:https://daveceddia.com/intro-to-hooks/

關於Fundebug

Fundebug專注於JavaScript、微信小程序、微信小遊戲、支付寶小程序、React Native、Node.js和Java線上應用實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了20億+錯誤事件,付費客戶有陽光保險、核桃編程、荔枝FM、掌門1對1、微脈、青團社等衆多品牌企業。歡迎大家免費試用

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章