首頁 > 軟體

一起來了解React的Hook

2022-03-16 13:00:11

State Hook

這個例子用來顯示一個計數器。當你點選按鈕,計數器的值就會增加:

import React, { useState } from 'react';
function Example() {
  // 宣告一個叫 「count」 的 state 變數。
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

在這裡,useState 就是一個 Hook (等下我們會講到這是什麼意思)。通過在函陣列件裡呼叫它來給元件新增一些內部 state。React 會在重複渲染時保留這個 stateuseState 會返回一對值:當前狀態一個讓你更新它的函數,你可以在事件處理常式中或其他一些地方呼叫這個函數。它類似 class 元件的 this.setState,但是它不會把新的 state 和舊的 state 進行合併。(我們會在使用 State Hook 裡展示一個對比 useState 和 this.state 的例子)。

useState 唯一的引數就是初始 state。在上面的例子中,我們的計數器是從零開始的,所以初始 state 就是 0。值得注意的是,不同於 this.state,這裡的 state 不一定要是一個物件 —— 如果你有需要,它也可以是。這個初始 state 引數只有在第一次渲染時會被用到。

宣告多個 state 變數

你可以在一個元件中多次使用 State Hook:

function ExampleWithManyStates() {
  // 宣告多個 state 變數!
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
  // ...
}

陣列解構的語法讓我們在呼叫 useState 時可以給 state 變數取不同的名字。當然,這些名字並不是 useState API 的一部分。React 假設當你多次呼叫 useState 的時候,你能保證每次渲染時它們的呼叫順序是不變的。後面我們會再次解釋它是如何工作的以及在什麼場景下使用。

那麼,什麼是 Hook?

Hook 是一些可以讓你在函陣列件裡 " 鉤入" React state 及生命週期等特性的函數。Hook 不能在 class 元件中使用 —— 這使得你不使用 class 也能使用 React。(我們不推薦把你已有的元件全部重寫,但是你可以在新元件裡開始使用 Hook。)

React 內建了一些像 useState 這樣的 Hook。你也可以建立你自己的 Hook 來複用不同元件之間的狀態邏輯。我們會先介紹這些內建的 Hook。

Effect Hook

你之前可能已經在 React 元件中執行過資料獲取、訂閱或者手動修改過 DOM。我們統一把這些操作稱為“副作用”,或者簡稱為“作用”。

useEffect 就是一個 Effect Hook,給函陣列件增加了操作副作用的能力。它跟 class 元件中的 componentDidMountcomponentDidUpdate 和 componentWillUnmount 具有相同的用途,只不過被合併成了一個 API。

例如,下面這個元件在 React 更新 DOM 後會設定一個頁面標題:

import React, { useState, useEffect } from 'react';
function Example() {
  const [count, setCount] = useState(0);
  // 相當於 componentDidMount 和 componentDidUpdate:
  useEffect(() => {
    // 使用瀏覽器的 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 在完成對 DOM 的更改後執行你的“副作用”函數。由於副作用函數是在元件內宣告的,所以它們可以存取到元件的 props 和 state。預設情況下,React 會在每次渲染後呼叫副作用函數 —— 包括第一次渲染的時候。

副作用函數還可以通過返回一個函數來指定如何“清除”副作用。例如,在下面的元件中使用副作用函數來訂閱好友的線上狀態,並通過取消訂閱來進行清除操作:

import React, { 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 的 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);
  }
  // ...

通過使用 Hook,你可以把元件內相關的副作用組織在一起(例如建立訂閱及取消訂閱),而不要把它們拆分到不同的生命週期函數裡。

Hook 使用規則

Hook 就是 JavaScript 函數,但是使用它們會有兩個額外的規則:

  • 只能在函數最外層呼叫 Hook。不要在迴圈、條件判斷或者子函數中呼叫。
  • 只能在 React 的函陣列件中呼叫 Hook。不要在其他 JavaScript 函數中呼叫。(還有一個地方可以呼叫 Hook —— 就是自定義的 Hook 中,我們稍後會學習到。)

自定義 Hook

有時候我們會想要在元件之間重用一些狀態邏輯。目前為止,有兩種主流方案來解決這個問題:高階元件和 render props。自定義 Hook 可以讓你在不增加元件的情況下達到同樣的目的。

前面,我們介紹了一個叫 FriendStatus 的元件,它通過呼叫 useState 和 useEffect 的 Hook 來訂閱一個好友的線上狀態。假設我們想在另一個元件裡重用這個訂閱邏輯。

首先,我們把這個邏輯抽取到一個叫做 useFriendStatus 的自定義 Hook 裡:

import React, { 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;
}

它將 friendID 作為引數,並返回該好友是否線上:

現在我們可以在兩個元件中使用它:

function FriendStatus(props) {
  const isOnline = useFriendStatus(props.friend.id);
  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}
function FriendListItem(props) {
  const isOnline = useFriendStatus(props.friend.id);
  return (
    <li style={{ color: isOnline ? 'green' : 'black' }}>
      {props.friend.name}
    </li>
  );
}

每個元件間的 state 是完全獨立的。Hook 是一種複用狀態邏輯的方式,它不復用 state 本身。事實上 Hook 的每次呼叫都有一個完全獨立的 state —— 因此你可以在單個元件中多次呼叫同一個自定義 Hook。

自定義 Hook 更像是一種約定而不是功能。如果函數的名字以 use 開頭並呼叫其他 Hook,我們就說這是一個自定義 Hook。 useSomething 的命名約定可以讓我們的 linter 外掛在使用 Hook 的程式碼中找到 bug。

你可以建立涵蓋各種場景的自定義 Hook,如表單處理、動畫、訂閱宣告、計時器,甚至可能還有更多我們沒想到的場景。我們很期待看到 React 社群會出現什麼樣的自定義 Hook。

其他 Hook

除此之外,還有一些使用頻率較低的但是很有用的 Hook。比如,useContext 讓你不使用元件巢狀就可以訂閱 React 的 Context。

function Example() {
  const locale = useContext(LocaleContext);
  const theme = useContext(ThemeContext);
  // ...
}

另外 useReducer 可以讓你通過 reducer 來管理元件原生的複雜 state

function Todos() {
  const [todos, dispatch] = useReducer(todosReducer);
  // ...

總結

本篇文章就到這裡了,希望能夠給你帶來幫助,也希望您能夠多多關注it145.com的更多內容!   


IT145.com E-mail:sddin#qq.com