React Hook


基本概念

Hook 是能讓你在函數組件中“鈎入” React 特性的函數,它們名字通常都以 use 開始。

Hook 使用了 JavaScript 的閉包機制,而不用在 JavaScript 已經提供了解決方案的情況下,還引入特定的 React API。

State Hook

function ExampleWithManyStates() {
  // 聲明多個 state 變量
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: '學習 Hook' }]);

State 變量可以很好地存儲對象和數組,因此,你仍然可以將相關數據分為一組。然而,不像 class 中的 this.setState,更新 state 變量總是替換它而不是合並它。

函數式更新

如果新的 state 需要通過使用先前的 state 計算得出,那么可以將函數傳遞給 setState。該函數將接收先前的 state,並返回一個更新后的值。

function Counter({initialCount}) {
  const [count, setCount] = useState(initialCount);
  return (
    <>
      Count: {count}
      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
    </>
  );
}

與 class 組件中的 setState 方法不同,useState 不會自動合並更新對象。你可以用函數式的 setState 結合展開運算符來達到合並更新對象的效果。

setState(prevState => {
  // 也可以使用 Object.assign
  return {...prevState, ...updatedValues};
});

Effect Hook

通過使用這個 Hook,你可以告訴 React 組件需要在渲染后執行某些操作。

你可能會更容易接受 effect 發生在“渲染之后”這種概念,不用再去考慮“掛載”還是“更新”。

如果你熟悉 React class 的生命周期函數,你可以把 useEffect Hook 看做 componentDidMountcomponentDidUpdatecomponentWillUnmount 這三個函數的組合。

componentDidMountcomponentDidUpdate 不同,使用 useEffect 調度的 effect 不會阻塞瀏覽器更新屏幕,這讓你的應用看起來響應更快。大多數情況下,effect 不需要同步地執行。在個別情況下(例如測量布局),有單獨的 useLayoutEffect Hook 供你使用,其 API 與 useEffect 相同。

如果你的 effect 返回一個函數,React 將會在執行清除操作時調用它。 React 在執行當前 effect 之前對上一個 effect 進行清除。

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

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

Hook 允許我們按照代碼的用途分離他們, 而不是像生命周期函數那樣。

在某些情況下,每次渲染后都執行清理或者執行 effect 可能會導致性能問題。在 class 組件中,我們可以通過在 componentDidUpdate 中添加對 prevPropsprevState 的比較邏輯解決:

componentDidUpdate(prevProps, prevState) {
  if (prevState.count !== this.state.count) {
    document.title = `You clicked ${this.state.count} times`;
  }
}

性能優化

你可以通知 React 跳過對 effect 的調用,只要傳遞數組作為 useEffect 的第二個可選參數即可:

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 僅在 count 更改時更新

如果想執行只運行一次的 effect(僅在組件掛載和卸載時執行),可以傳遞一個空數組([])作為第二個參數。

要記住 effect 外部的函數使用了哪些 props 和 state 很難。這也是為什么 通常你會想要在 effect 內部 去聲明它所需要的函數。

Hook 規則

  • 只能在函數最外層調用 Hook。不要在循環、條件判斷或者子函數中調用。
  • 只能在 React 的函數組件中調用 Hook。不要在其他 JavaScript 函數中調用。

那么 React 怎么知道哪個 state 對應哪個 useState?答案是 React 靠的是 Hook 調用的順序。因為我們的示例中,Hook 的調用順序在每次渲染中都是相同的,所以它能夠正常工作:

// ------------
// 首次渲染
// ------------
useState('Mary')           // 1. 使用 'Mary' 初始化變量名為 name 的 state
useEffect(persistForm)     // 2. 添加 effect 以保存 form 操作
useState('Poppins')        // 3. 使用 'Poppins' 初始化變量名為 surname 的 state
useEffect(updateTitle)     // 4. 添加 effect 以更新標題

// -------------
// 二次渲染
// -------------
useState('Mary')           // 1. 讀取變量名為 name 的 state(參數被忽略)
useEffect(persistForm)     // 2. 替換保存 form 的 effect
useState('Poppins')        // 3. 讀取變量名為 surname 的 state(參數被忽略)
useEffect(updateTitle)     // 4. 替換更新標題的 effect

// ...

如果我們想要有條件地執行一個 effect,可以將判斷放到 Hook 的內部

自定義 Hook

自定義 Hook 是一個函數,其名稱以 “use” 開頭,函數內部可以調用其他的 Hook。以use開頭的原因是react需要檢查你的 Hook 是否違反了 Hook 的規則

自定義 Hook 不需要具有特殊的標識。我們可以自由的決定它的參數是什么,以及它應該返回什么(如果需要的話)。

自定義 Hook 是一種自然遵循 Hook 設計的約定,而並不是 React 的特性。

import React, { useState, useEffect } from 'react';

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

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

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

  return isOnline;
}

本質上是用一個公共函數將多個Hook方法進行了封裝


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM