1. 程式人生 > >react 16.7 hooks write custom 編寫自己的鉤子

react 16.7 hooks write custom 編寫自己的鉤子

構建自己的Hook可以將元件邏輯提取到可重用的函式中。

當我們學習使用`Effect Hook時,我們從聊天應用程式中看到了這個元件,該元件顯示一條訊息,指示朋友是線上還是離線:

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'; }

現在讓我們的聊天應用程式也有一個聯絡人列表,我們想要呈現綠色的線上使用者名稱。我們可以將上面類似的邏輯複製並貼上到我們的FriendListItem元件中,但它不是很好:

import { useState, useEffect } from 'react';

function FriendListItem(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); }; }); return ( <li style={{ color: isOnline ? 'green' : 'black' }}> {props.friend.name} </li> ); }

相反,我們想在FriendStatusFriendListItem之間分享這個邏輯。

傳統上,在React中,我們有兩種流行的方式來共享元件之間的狀態邏輯:渲染道具和高階元件。我們現在將看看Hook如何在不新增這些方法的情況下解決許多相同的問題。

提取自定義鉤子

當我們想要在兩個JavaScript函式之間共享邏輯時,我們將它提取到第三個函式。元件和掛鉤都是功能,所以這也適用於它們!

自定義Hook其實也是一個JavaScript函式,其名稱以use開頭,可以呼叫其他Hook 例如,下面的useFriendStatus是我們的第一個自定義鉤子:

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;
}

裡面沒有任何新內容 - 邏輯是從上面的元件中複製的。就像在元件中一樣,確保只在自定義Hook的頂層無條件地呼叫其他Hook

React元件不同,自定義Hook不需要具有特定簽名。我們可以決定它作為引數需要什麼,以及它應該返回什麼(如果有的話)。換句話說,它就像一個普通的功能。它的名字應該始終使用use開頭,這樣你就可以一眼就看出鉤子的規則適用於它。

我們使用FriendStatus Hook的目的是訂閱我們朋友的狀態。這就是為什麼它將friendID作為引數,並返回此朋友是否線上:

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

  return isOnline;
}

使用自定義鉤子

最初,我們的目標是從FriendStatusFriendListItem元件中刪除重複的邏輯。他們倆都想知道朋友是否線上。

現在我們已經將這個邏輯提取到useFriendStatus鉤子,我們可以使用它:

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>
  );
}

這段程式碼是否等同於原始示例? 是的,他們以相同的方式工作。如果你仔細觀察,你會注意到我們沒有對行為做任何改變。我們所做的只是將兩個函式之間的一些公共程式碼提取到一個單獨的函式中。自定義掛鉤是一種自然遵循Hooks設計的約定,而不是React功能。

我是否必須以use開頭命名我的自定義Hook 我們希望你做到這點。這個習慣很重要。如果沒有它,我們將無法自動檢查是否違反了Hook規則,因為我們無法判斷某個函式是否包含對其中的Hooks的呼叫。

兩個元件使用相同的Hook共享狀態嗎? 不會。自定義掛鉤是一種重用有狀態邏輯的機制(例如設定訂閱和記住當前值),但每次使用自定義掛鉤時,自定義掛鉤的所有狀態和效果都是完全獨立的。

自定義Hook如何獲得隔離狀態? 每次對Hook的呼叫都會被隔離。因為我們直接呼叫useFriendStatus,從React的角度來看,我們的元件只調用useStateuseEffect。正如我們之前-所知,我們可以在一個元件中多次呼叫useStateuseEffect,它們將完全獨立。

提示:在掛鉤之間傳遞資訊

由於Hooks是函式,我們可以在它們之間傳遞資訊。

為了說明這一點,我們將使用我們假設的聊天示例中的另一個元件。這是一個聊天訊息收件人選擇器,顯示當前所選朋友是否線上:

const friendList = [
  { id: 1, name: 'Phoebe' },
  { id: 2, name: 'Rachel' },
  { id: 3, name: 'Ross' },
];

function ChatRecipientPicker() {
  const [recipientID, setRecipientID] = useState(1);
  const isRecipientOnline = useFriendStatus(recipientID);

  return (
    <>
      <Circle color={isRecipientOnline ? 'green' : 'red'} />
      <select
        value={recipientID}
        onChange={e => setRecipientID(Number(e.target.value))}
      >
        {friendList.map(friend => (
          <option key={friend.id} value={friend.id}>
            {friend.name}
          </option>
        ))}
      </select>
    </>
  );
}

我們將當前選擇的friend ID保留在recipientID狀態變數中,如果使用者在<select>選擇器中選擇其他朋友,則更新它。

因為useState Hook呼叫為我們提供了recipientID狀態變數的最新值,所以我們可以將它作為引數傳遞給我們的自定義useFriendStatus Hook

const [recipientID, setRecipientID] = useState(1);
const isRecipientOnline = useFriendStatus(recipientID);

這讓我們知道當前選擇的朋友是否線上。如果我們選擇不同的朋友並更新recipientID狀態變數,我們的useFriendStatus Hook將取消訂閱之前選擇的朋友,並訂閱新選擇的朋友的狀態。

使用你想象中的鉤子

Custom Hooks提供了以前在React元件中無法實現的共享邏輯的靈活性。您可以編寫自定義Hook,涵蓋廣泛的用例,如表單處理,動畫,宣告訂閱,計時器,以及可能還有更多我們沒有考慮過的。更重要的是,可以構建與React的內建功能一樣易於使用的Hook

儘量抵制過早新增抽象。既然功能元件可以做得更多,那麼程式碼庫中的平均功能元件可能會變得更長。這是正常的 - 不要覺得你必須立即將它分成鉤子。但我們也鼓勵您開始發現自定義Hook可以隱藏簡單介面背後的複雜邏輯或幫助解開凌亂元件的情況。

例如,可能有一個複雜的元件,其中包含許多以ad-hoc方式管理的本地狀態。 useState不會使更新邏輯更容易集中化,因此你可能希望將其編寫為Redux reducer

function todosReducer(state, action) {
  switch (action.type) {
    case 'add':
      return [...state, {
        text: action.text,
        completed: false
      }];
    // ... other actions ...
    default:
      return state;
  }
}

reducer非常便於單獨測試,並且可以擴充套件以表達複雜的更新邏輯。如有必要,可以將它們分成更小的reducer。但是,你可能還享受使用React本地狀態的好處,或者可能不想安裝其他庫。

那麼,如果我們可以編寫一個useReducer Hook,讓我們使用reducer管理元件的本地狀態呢?它的簡化版本可能如下所示:

function useReducer(reducer, initialState) {
  const [state, setState] = useState(initialState);

  function dispatch(action) {
    const nextState = reducer(state, action);
    setState(nextState);
  }

  return [state, dispatch];
}

現在我們可以在我們的元件中使用它,讓reducer驅動它的狀態管理:

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

  function handleAddClick(text) {
    dispatch({ type: 'add', text });
  }

  // ...
}

在複雜元件中使用reducer管理本地狀態的需求很常見,我們已經將useReducer Hook構建到React中。你可以在Hooks API參考中找到它與其他內建Hook一起使用。

更多hooks系列api前點選此處檢視