React Hooks-概覽

鯊叔發表於2019-03-01

前言

本文為意譯,翻譯過程中摻雜本人的理解,如有誤導,請放棄繼續閱讀。

原文地址:Hooks at a Glance

正文

Hooks是即將到來的React特性。這個特效能讓你不需要編寫class component的前提下也能使用state和其他React特性。當前,在React v16.7.0-alpha版本中,它們是可用的。

Hooks是React中一個向後相容的特性。這篇文件將會為React老手們提供一個Hooks特性的概覽。

一. 什麼是Hook?

Hooks是一個函式。它能像鉤子一樣,讓你在函式元件的內部也能“鉤住”React state,生命週期函式等React特性。Hooks不能在類(classes)裡面使用,它能讓你在不使用類的前提下來編寫React應用。注意,我們並沒有推薦你馬上就重寫現有的元件。但是如果你想的話,你可以在一些新元件上試用一下。

React提供了一些內建的Hook-比如useState。你也可以建立一些自己的custom Hook來在元件間複用某些狀態型的邏輯和行為。首先,我們來看看那些內建的Hook。

二. 內建React Hook

1.State Hook

以下示例程式碼渲染的是counter元件。當你點選按鈕的時候,當前的count值就增加1。

import { useState } from `react`;

function Example() {
  // Declare a new state variable, which we`ll call "count"
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
複製程式碼

在這裡,useState就是Hook。我們在一個function component裡面呼叫它來為這個元件新增了一個local state-count。React負責在重複渲染過程中儲存這個local state。呼叫useState將會返回了一對值:當前state值和一個負責更新這個state的函式(updater)。你可以在event handler或者別的地方來呼叫這個updater。它相當於class component中的this.setState。不同的是,Hook返回的這個updater並不會合併新老state。關於這兩者(useState所返回的updater和this.state)的比較,我們會在這個章節進行說明。

useState的唯一引數就是state的初始值。在上面的例子中,這個初始值就是0。因為我們的counter是從0開始的。注意,不像this.state,這裡的state的初始值並不要求一定是一個javascript字面量物件。不過,如果你喜歡的話,物件也是可以的。Hook的初始state值只會在第一次渲染的時候用到。

除了上述提到的,用Hook生成的state不一定是一個物件外,Hook的state跟class component的state的還有一個不同點。它就是:Hook state可以有多個。也就是說,你可以在一個function component中使用多次state Hook:

function ExampleWithManyStates() {
  // Declare multiple state variables!
  const [age, setAge] = useState(42);
  const [todos, setTodos] = useState([{ text: `Learn Hooks` }]);
  const [fruit, setFruit] = useState(`banana`);
  // ...
}
複製程式碼

這裡的陣列解構語法使得我們可以對呼叫useState所返回的state變數進行命名。這個命名並不是useState這個API語法的一部分,所以,叫什麼都不重要。React在這裡作了一個假設。什麼假設呢?那就假設你在每一次渲染過程中所呼叫useState的順序是一樣的。我們稍後再來討論為什麼要這麼做。

2.Effect Hook

你以前一定在React元件裡面做過資料獲取,事件訂閱或者DOM操作等行為。我們把這些行為稱之為“副作用(side effects)”。這是因為這些行為會影響到其他的元件,並且不能發生在render期間。

effect hook – useEffect,它為function component新增了執行副作用的能力。這在hook特性加入到React之前是辦不到的。useEffect這個hook起到的作用跟class component中的componentDidMount,componentDidUpdate和componentWillUnmount等生命週期函式的作用是一樣的。不同的是,useEffect將這三個方法的程式碼合成到一個API裡面了。稍後,我們會在使用Effect Hook章節中闡述useEffect和這三個方法的異同。

舉個例子,下面這個例子將會在React更新完DOM之後去設定文件的標題:

import { useState, useEffect } from `react`;

function Example() {
  const [count, setCount] = useState(0);

  // Similar to componentDidMount and componentDidUpdate:
  useEffect(() => {
    // Update the document title using the browser API
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
複製程式碼

當你呼叫useEffect的時候,相當於你在告訴React,你想在[將對virtual DOM的更新對映到真實DOM]之後執行你的副作用。副作用宣告在元件的內部,這樣一來,副作用就能訪問元件自身的props和state了。預設情況下,React會在每次渲染之後都會執行你的副作用,這裡麵包括了第一次渲染。

副作用也可以可選地通過返回一個函式來告知React如何去做一些清除(clean up)副作用的工作。舉個例子,下面這個元件使用了一個effect去訂閱了某個朋友的線上狀態,然後通過取消訂閱來做一些清除工作:

import { useState, useEffect } from `react`;

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);

  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);

    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  if (isOnline === null) {
    return `Loading...`;
  }
  return isOnline ? `Online` : `Offline`;
}
複製程式碼

在這裡例子中,React將會在元件解除安裝的時候取消對ChatAPI的訂閱。在每次重新渲染之前也是一樣的,也會取消對ChatAPI的訂閱。如果你不想每次渲染之前後都這樣,你也可以告訴React跳過某些訂閱-只有props.friend.id變了才重新去訂閱。至於如何告訴React跳過某些訂閱呢,請檢視這裡

跟useState一樣,你也可以在一個元件的內部多次呼叫useEffect:

function FriendStatusWithCounter(props) {
  const [count, setCount] = useState(0);
  // 這是第一次呼叫
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  const [isOnline, setIsOnline] = useState(null);
  // 這是第二次呼叫
  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }
  // ...
複製程式碼

Hooks可以讓我們能夠根據元件內部的哪塊程式碼是相關的來組織我們的程式碼(比如說,訂閱和取消訂閱的程式碼就是想關聯的),而不是像以前那樣簡單粗暴地逼你只能基於生命週期方法來做程式碼的分離。

3.其他內建的Hooks

除了,useState和useEffect這兩個比較常用的Hook之外,React還提供了一些比較少用但是卻很有用的Hook來給大家。比如,useContext就是其中一個。它能讓你在不引入額外的元件樹層級的情況下訂閱到React context:

function Example() {
  const locale = useContext(LocaleContext);
  const theme = useContext(ThemeContext);
  // ...
}
複製程式碼

再比如useReducer這個Hook。它能讓你用一個reducer去管理元件複雜的本地state:

function Todos() {
  const [todos, dispatch] = useReducer(todosReducer);
  // ...
複製程式碼

以上只是拋磚引玉,更多內建的Hook API以及其使用細節請到Hooks API Reference裡面查閱。

三. 打造你自己的Hooks

有時候,我們想在元件間複用一些狀態型邏輯。傳統(Hooks沒出來前)的做法是使用高階元件和render props兩種技術來實現對這行狀態型邏輯的封裝。現在Hook來了。它能讓你在不引入額外的元件樹層級的前提下達成同樣的目標。

在這篇文件的前面部分,我們介紹了一個叫FriendStatus的元件。這個元件的內部通過呼叫useState和useEffect這兩個Hook來實現了對朋友線上狀態的訂閱。假如我們想在別的元件複用這段訂閱邏輯程式碼,那我們該怎麼辦呢?

首先,我們應該提取這段邏輯到一個custom Hook裡面。我們給這個custom Hook命名為useFriendStatus:

import { useState, useEffect } from `react`;

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;
}
複製程式碼

這個custom Hook接收friendID作為它的引數,最後返回一個boolean值來標識該使用者是否線上。

然後,我們就可以像使用 built in Hook一樣使用它了:

// 在A元件我們是這樣用
function FriendStatus(props) {
  const isOnline = useFriendStatus(props.friend.id);

  if (isOnline === null) {
    return `Loading...`;
  }
  return isOnline ? `Online` : `Offline`;
}

// 在B元件我們又是這樣用
function FriendListItem(props) {
  const isOnline = useFriendStatus(props.friend.id);

  return (
    <li style={{ color: isOnline ? `green` : `black` }}>
      {props.friend.name}
    </li>
  );
}
複製程式碼

以上兩個元件的state是完全獨立的。 準確地來說,Hooks是關於複用狀態型邏輯(stateful logic)而不是複用狀態(state)本身的一種方案。實際上,每一次呼叫Hook所生成的state是完全獨立的。所以你完全可以在一個元件內部呼叫多次同一個custom Hook。

你可以編寫自己的custom Hook來覆蓋許多常見的業務場景。比如說,表單處理,動畫,宣告式的訂閱,定時器等等。此外,還有很多我們沒想得到的場景,你自己也可以想想如何使用custom Hook來完成封裝。我們很期待React社群將會推出什麼樣的custom Hooks!

從另外一個角度來講,custom Hook更多是一種約定俗成的寫法而不是一種特性。如果一個javascript函式的函式名是以“use”開頭,並且在函式內部裡面呼叫了其他的Hook(built in Hook或者custom Hook),那麼我們就可以稱這個函式為custom Hook。之所以約定使用“useSomething”這種命名法,是為了支援linter plugin運用Hooks規則對其進行檢查。

關於custom Hooks的更多內容,請檢視Building Your Own Hooks

四. Hooks的鐵規

Hooks是普通的avascript函式。但是不同於普通的javascript函式,React為Hooks強加了兩條額外的規則:

  • 只能在當前作用域的頂層來呼叫Hooks。不能在迴圈語句,條件分支語句或者被巢狀函式裡面來呼叫Hooks。
  • 只能在React的function component中呼叫Hooks。不要在普通的javascript函式裡面呼叫Hooks。注意,還有另外一個地方可以呼叫Hooks。那就是custom Hooks。在custom Hooks中呼叫Hooks也要遵循第一條規則。

我們提供了一個linter plugin來自動地檢查使用者對Hooks的呼叫是否符合這兩條規則。當然,我們也知道這兩條規則看起來有點限制過頭或者令人困惑的。但是沒辦法啊,這兩條規則是保證Hooks有效發揮作用的必要前提。

你可以在Rules of Hooks專欄裡面查閱更多的細節。

相關文章