ReactJS - 自定義 Hook



Hooks 是函式元件不可分割的一部分。它們可以用來增強函式元件的功能。React 提供了一些內建 Hook。儘管內建 Hook 功能強大,可以實現任何功能,但專用 Hook 仍然是必要的,並且需求量很大。

React 瞭解了開發者的這種需求,並允許透過現有的 Hook 建立新的自定義 Hook。開發者可以從函式元件中提取特殊的功能,並將其建立為一個單獨的 Hook,可以在任何函式元件中使用。

讓我們在本節學習如何建立自定義 Hook。

建立自定義 Hook

讓我們建立一個具有無限滾動功能的新 React 函式元件,然後從函式元件中提取無限滾動功能並建立一個自定義 Hook。建立自定義 Hook 後,我們將嘗試更改原始函式元件以使用我們的自定義 Hook。

實現無限滾動功能

元件的基本功能是簡單地透過生成虛擬待辦事項列表來顯示它。當用戶滾動時,元件將生成一組新的虛擬待辦事項列表並將其附加到現有列表。

首先,建立一個新的 React 應用,並使用以下命令啟動它。

create-react-app myapp
cd myapp
npm start

接下來,在元件資料夾下(src/components/TodoList.js)建立一個 React 元件 TodoList。

function TodoList() {
   return <div>Todo List</div>
}
export default TodoList

接下來,更新根元件 App.js 以使用新建立的 TodoList 元件。

import TodoList from './components/TodoList';
function App() {
   return (
      <div style={{ padding: "5px"}}>
         <TodoList />
      </div>
   );
}
export default App;

接下來,建立一個計數狀態變數來維護要生成的待辦事項數量。

const [count, setCount] = useState(100)

這裡:

  • 初始要生成的專案數量為 100

  • count 是用於維護待辦事項數量的狀態變數

接下來,建立一個數據陣列來維護生成的虛擬待辦事項,並使用 useMemo Hook 保留引用。

React.useMemo(() => {
   for (let i = 1; i <= count; i++) {
      data.push({
         id: i,
         todo: "Todo Item " + i.toString()
      })
   }
}, [count]);

這裡:

  • 使用 useMemo 來限制每次渲染時待辦事項的生成。

  • 使用 count 作為依賴項,以便在計數更改時重新生成待辦事項

接下來,將處理程式附加到滾動事件,並在使用者移動到頁面末尾時更新要生成的待辦事項計數。

React.useEffect(() => {
   function handleScroll() {
      const isBottom =
         window.innerHeight + document.documentElement.scrollTop
         === document.documentElement.offsetHeight;
      if (isBottom) {
         setCount(count + 100)
      }
   }
   window.addEventListener("scroll", handleScroll);
   return () => {
      window.removeEventListener("scroll", handleScroll);
   };
}, [])

這裡我們有:

  • 使用 useEffect Hook 確保 DOM 已準備好附加事件。

  • 為滾動事件附加了一個事件處理程式 scrollHandler

  • 當元件從應用程式解除安裝時,刪除事件處理程式

  • 在滾動事件處理程式中使用 DOM 屬性查詢使用者是否到達頁面底部。

  • 一旦使用者到達頁面底部,計數就會增加 100

  • 一旦計數狀態變數更改,元件就會重新渲染,並且會載入更多待辦事項。

最後,按如下所示渲染待辦事項:

<div>
   <p>List of Todo list</p>
   <ul>
      {data && data.map((item) =>
         <li key={item.id}>{item.todo}</li>
      )}
   </ul>
</div>

元件的完整原始碼如下:

import React, { useState } from "react"
function TodoList() {
   const [count, setCount] = useState(100)
   let data = []
   
   React.useMemo(() => {
      for (let i = 1; i <= count; i++) {
         data.push({
            id: i,
            todo: "Todo Item " + i.toString()
         })
      }
   }, [count]);
   
   React.useEffect(() => {
      function handleScroll() {
         const isBottom =
            window.innerHeight + document.documentElement.scrollTop
            === document.documentElement.offsetHeight;
         
         if (isBottom) {
            setCount(count + 100)
         }
      }
      window.addEventListener("scroll", handleScroll);
      return () => {
         window.removeEventListener("scroll", handleScroll);
      };
   }, [])
   return (
      <div>
         <p>List of Todo list</p>
         <ul>
            {data && data.map((item) =>
               <li key={item.id}>{item.todo}</li>
            )}
         </ul>
      </div>
   )
}
export default TodoList

接下來,開啟瀏覽器並執行應用程式。應用程式將在使用者到達頁面末尾時附加新的待辦事項,並無限期地繼續,如下所示:

Implement Infinite Scroll Functionality

實現useInfiniteScroll Hook

接下來,讓我們嘗試透過從現有元件中提取邏輯來建立新的自定義 Hook,然後在單獨的元件中使用它。建立一個新的自定義 Hook,useInfiniteScroll (src/Hooks/useInfiniteScroll.js),如下所示

import React from "react";
export default function useInfiniteScroll(loadDataFn) {
   const [bottom, setBottom] = React.useState(false);
   React.useEffect(() => {
      window.addEventListener("scroll", handleScroll);
      return () => {
         window.removeEventListener("scroll", handleScroll);
      };
   }, []);
   React.useEffect(() => {
      if (!bottom) return;
      loadDataFn();
   }, [bottom]);
   
   function handleScroll() {
      if (window.innerHeight + document.documentElement.scrollTop
         != document.documentElement.offsetHeight) {
         return;
      }
      setBottom(true)
   }
   return [bottom, setBottom];
}

這裡我們有:

  • 使用use關鍵字命名自定義 Hook。這是使用use關鍵字作為自定義 Hook 名稱開頭的慣例,它也是 React 的提示,提示該函式是一個自定義 Hook

  • 使用狀態變數 bottom 來了解使用者是否到達頁面底部。

  • 使用 useEffect 在 DOM 可用後註冊滾動事件

  • 使用泛型函式 loadDataFn 在元件中建立 Hook 時提供。這將能夠為載入資料建立自定義邏輯。

  • 在滾動事件處理程式中使用 DOM 屬性來跟蹤使用者滾動位置。當用戶到達頁面底部時,bottom 狀態變數會發生變化。

  • 返回 bottom 狀態變數的當前值 (bottom) 和用於更新 bottom 狀態變數的函式 (setBottom)

接下來,建立一個新元件 TodoListUsingCustomHook 來應用新建立的 Hook。

function TodoListUsingCustomHook() {
   return <div>Todo List</div>
}
export default TodoListUsingCustomHook

接下來,更新根元件 App.js 以使用新建立的 TodoListUsingCustomHook 元件。

import TodoListUsingCustomHook from './components/TodoListUsingCustomHook';
function App() {
   return (
      <div style={{ padding: "5px"}}>
         <TodoListUsingCustomHook />
      </div>
   );
}
export default App;

接下來,建立一個狀態變數 data 來管理待辦事項列表。

const [data, setData] = useState([])

接下來,建立一個狀態變數 count 來管理要生成的待辦事項列表的數量。

const [data, setData] = useState([])

接下來,應用無限滾動自定義 Hook。

const [bottom, setBottom] = useInfiniteScroll(loadMoreData)

這裡,bottomsetBottomuseInfiniteScroll Hook 公開。

接下來,建立用於生成待辦事項列表的函式 loadMoreData

function loadMoreData() {
   let data = []
   for (let i = 1; i <= count; i++) {
      data.push({
         id: i,
         todo: "Todo Item " + i.toString()
      })
   }
   setData(data)
   setCount(count + 100)
   setBottom(false)
}

這裡:

  • 根據計數狀態變數生成待辦事項

  • 將計數狀態變數增加 100

  • 使用 setData 函式將生成的待辦事項列表設定為 data 狀態變數

  • 使用 setBottom 函式將 bottom 狀態變數設定為 false

接下來,生成初始待辦事項,如下所示:

const loadData = () => {
   let data = []
   for (let i = 1; i <= count; i++) {
      data.push({
         id: i,
         todo: "Todo Item " + i.toString()
      })
   }
   setData(data)
}
React.useEffect(() => {
   loadData(count)
}, [])

這裡:

  • 初始待辦事項是根據初始計數狀態變數生成的

  • 使用 setData 函式將生成的待辦事項列表設定為 data 狀態變數

  • 使用 useEffect 確保在 DOM 可用後生成資料

最後,按如下所示渲染待辦事項:

<div>
   <p>List of Todo list</p>
   <ul>
      {data && data.map((item) =>
         <li key={item.id}>{item.todo}</li>
      )}
   </ul>
</div>

元件的完整原始碼如下所示:

import React, { useState } from "react"
import useInfiniteScroll from '../Hooks/useInfiniteScroll'

function TodoListUsingCustomHook() {
   const [data, setData] = useState([])
   const [count, setCount] = useState(100)
   const [bottom, setBottom] = useInfiniteScroll(loadMoreData)
   
   const loadData = () => {
      let data = []
      for (let i = 1; i <= count; i++) {
         data.push({
            id: i,
            todo: "Todo Item " + i.toString()
         })
      }
      setData(data)
   }
  
  function loadMoreData() {
      let data = []
      
      for (let i = 1; i <= count; i++) {
         data.push({
            id: i,
            todo: "Todo Item " + i.toString()
         })
      }
      setData(data)
      setCount(count + 100)
      setBottom(false)
   }
   React.useEffect(() => {
      loadData(count)
   }, [])
   return (
      <div>
         <p>List of Todo list</p>
         <ul>
            {data && data.map((item) =>
               <li key={item.id}>{item.todo}</li>
            )}
         </ul>
      </div>
   )
}
export default TodoListUsingCustomHook

最後,開啟瀏覽器並檢查輸出。應用程式將在使用者到達頁面末尾時附加新的待辦事項,並無限期地繼續,如下所示:

Implementing UseInfiniteScroll Hook

行為與 TodoList 元件相同。我們已成功地從元件中提取邏輯並將其用於建立我們的第一個自定義 Hook。現在,可以在任何應用程式中使用自定義 Hook。

總結

自定義 Hook 是一種現有的功能,用於將可重用的邏輯與函式元件分離,並允許在不同的函式元件中使其成為真正可重用的 Hook。

廣告