React Hooks:讓你不必寫 class 就能使用 state 和其他 React 功能
前言
React Hooks 是 React 16.8 中引入的革命性功能,它徹底改變了我們編寫 React 元件的方式。Hooks 讓開發者無需使用 class 就能在函數元件中使用 state 和其他 React 功能,簡化了程式碼結構並提高了程式碼的可重用性。
過去使用元件的方式
Class Component
在 Hooks 出現之前,如果我們需要在元件中使用 state 或生命週期方法,就必須使用 class component:
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
componentDidMount() {
document.title = `You clicked ${this.state.count} times`;
}
componentDidUpdate() {
document.title = `You clicked ${this.state.count} times`;
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}
Function Component
函數元件在 Hooks 之前只能用於簡單的、無狀態的元件:
function Greeting(props) {
return <h1>Hello, {props.name}</h1>;
}
Hooks 的引入
Hooks 讓我們可以在函數元件中使用 state 和其他 React 功能,無需轉換為 class component。主要解決了以下問題:
- 狀態邏輯重用困難 - 以前需要使用高階元件或 render props 模式
- 複雜元件難以理解 - 生命週期方法經常包含不相關的邏輯
- class 的學習成本 - 需要理解 JavaScript 中的
this
工作原理
State Hook (useState)
useState
是第一個也是最基礎的 Hook,它讓函數元件能夠擁有內部狀態:
import React, { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
Effect Hook (useEffect)
useEffect
讓你在函數元件中執行副作用操作,它結合了 class 元件中的 componentDidMount
、componentDidUpdate
和 componentWillUnmount
三個生命週期方法的功能:
import React, { useState, useEffect } from "react";
function Counter() {
const [count, setCount] = useState(0);
// 類似於 componentDidMount 和 componentDidUpdate:
useEffect(() => {
// 更新文件標題
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 元件與外部系統同步,而不是單純作為生命週期的替代品。
參考文章:
- https://ithelp.ithome.com.tw/users/20129300/ironman/5892
- https://www.youtube.com/watch?v=v9M20STEjgc
自定義 Hook (Custom Hook)
自定義 Hook 是一種遵循特定命名約定(以 "use" 開頭)的 JavaScript 函數,它可以調用其他 Hook:
import { useState, useEffect } from "react";
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
return width;
}
// 在元件中使用
function MyComponent() {
const width = useWindowWidth();
return <div>Window width: {width}</div>;
}
常見的自定義 Hook 示例
1. useMediaQuery
這個 Hook 讓你可以在 React 裡「偵測媒體查詢條件是否成立」,例如螢幕寬度是否小於 768px。 它會根據視窗大小自動更新狀態,讓你可以在元件裡用 isMobile 這種布林值,來做 RWD 或條件渲染。
適合場景:
- 響應式設計(RWD)
- 根據螢幕大小切換不同 UI 或功能
import { useState, useEffect } from "react";
function useMediaQuery(query) {
const [matches, setMatches] = useState(false);
useEffect(() => {
const media = window.matchMedia(query);
if (media.matches !== matches) {
setMatches(media.matches);
}
const listener = () => setMatches(media.matches);
media.addListener(listener);
return () => media.removeListener(listener);
}, [query, matches]);
return matches;
}
// 使用示例
function Component() {
const isMobile = useMediaQuery("(max-width: 768px)");
return <div>{isMobile ? "Mobile View" : "Desktop View"}</div>;
}
2. usePagination
usePagination 就是一個 headless 的 Pagination 元件,他已經幫你處理好資料邏輯,讓使用的開發者只需要關注在渲染邏輯。
你只要給它一份資料和每頁要顯示幾筆,它就會自動幫你算出目前頁面、最大頁數、當前要顯示的資料,還有上一頁、下一頁、跳頁等方法。
適合場景:
- 文章列表、商品列表等需要分頁的地方
- 想要把分頁邏輯抽離 UI,讓元件更乾淨
import { useState } from "react";
function usePagination(data, itemsPerPage) {
const [currentPage, setCurrentPage] = useState(1);
const maxPage = Math.ceil(data.length / itemsPerPage);
function currentData() {
const begin = (currentPage - 1) * itemsPerPage;
const end = begin + itemsPerPage;
return data.slice(begin, end);
}
function next() {
setCurrentPage((currentPage) => Math.min(currentPage + 1, maxPage));
}
function prev() {
setCurrentPage((currentPage) => Math.max(currentPage - 1, 1));
}
function jump(page) {
const pageNumber = Math.max(1, page);
setCurrentPage(Math.min(pageNumber, maxPage));
}
return { next, prev, jump, currentData, currentPage, maxPage };
}
// 使用示例
function PaginatedList({ data }) {
const { currentData, next, prev, currentPage, maxPage } = usePagination(
data,
5
);
return (
<div>
{/* 渲染 currentData */}
<button onClick={prev} disabled={currentPage === 1}>
Previous
</button>
<button onClick={next} disabled={currentPage === maxPage}>
Next
</button>
</div>
);
}
3. useDebounce
這個 Hook 幫你實現「防抖動」功能。防抖動是一種常用的最佳化技術,可以限制某個函數在短時間內被多次調用
,從而減少效能消耗和不必要的重複操作。
一般來說,防抖動的實作原理是透過延遲執行函數,在指定的時間內如果再次觸發,則重新計時。這對於一些需要等待使用者輸入完成或減少請求發送頻率的場景
非常有用。
適合場景:
- 搜尋框、即時過濾等需要減少頻繁請求的場景
- 想要優化效能、減少不必要的運算或 API 呼叫
import { useState, useEffect } from "react";
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}
// 使用示例
function SearchInput() {
const [query, setQuery] = useState("");
const debouncedQuery = useDebounce(query, 500);
useEffect(() => {
// 這裡可以執行搜索 API 調用
console.log("Searching for:", debouncedQuery);
}, [debouncedQuery]);
return (
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
/>
);
}
這些自定義 Hook 都是 React 開發中非常實用的範例,可以讓你的元件更簡潔、邏輯更好重用!
Hooks 的優點
- 更少的程式碼 - 消除了 class 的樣板程式碼,如 constructor 和 binding
- 簡化複雜的元件 - 將相關邏輯組織在一起,而不是分散在多個生命週期方法中
- 重用有狀態邏輯 - 通過自定義 Hook 輕鬆共享邏輯,無需高階元件或 render props
- 共享非視覺邏輯 - 可以在不改變元件層次結構的情況下共享邏輯
Hooks 的挑戰
- 學習曲線 - 特別是
useEffect
的正確使用需要時間掌握 - 常見陷阱 - 如
useCallback
和useMemo
的過度或不當使用 - 規則限制 - Hooks 只能在函數元件的最頂層調用,不能在條件或循環中使用
Hooks 使用規則
- 只在最頂層調用 Hook - 不要在循環、條件或巢狀函數中調用 Hook
- 只在 React 函數元件中調用 Hook - 不要在普通 JavaScript 函數中調用 Hook
- 自定義 Hook 必須以 "use" 開頭命名 - 這樣 React 才能識別並檢查 Hook 規則
結語
React Hooks 代表了 React 開發方式的重大進步,它簡化了狀態管理和副作用處理,使程式碼更加模塊化和可重用。雖然需要一些時間來適應新的思維模式,但一旦掌握,Hooks 將大大提高開發效率和程式碼品質。
隨著 React 生態系統的發展,Hooks 已經成為現代 React 開發的標準實踐,建議新專案都採用 Hooks 來編寫元件。