快速瞭解 React Hooks 原理

做者:Dave Ceddiahtml

譯者:前端小智前端

來源:daveceddiareact


阿里雲最近在作活動,低至2折,有興趣能夠看看promotion.aliyun.com/ntms/yunpar…git


爲了保證的可讀性,本文采用意譯而非直譯。github

咱們大部分 React 類組件能夠保存狀態,而函數組件不能? 而且類組件具備生命週期,而函數組件卻不能?數組

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

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

類被會替代嗎?

Hooks不會替換類,它們只是一個你可使用的新工具。React 團隊表示他們沒有計劃在React中棄用類,因此若是你想繼續使用它們,能夠繼續用。dom

我能體會那種總有新東西要學的感受有多痛苦,不會就感受我們老是落後同樣。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

原文:daveceddia.com/intro-to-ho…

交流(歡迎加入羣,羣工做日都會發紅包,互動討論技術)

乾貨系列文章彙總以下,以爲不錯點個Star,歡迎 加羣 互相學習。

github.com/qq449245884…

我是小智,公衆號「大遷世界」做者,對前端技術保持學習愛好者。我會常常分享本身所學所看的乾貨,在進階的路上,共勉!

關注公衆號,後臺回覆福利,便可看到福利,你懂的。

相關文章
相關標籤/搜索