跳至主要内容

37 篇文章 含有標籤「react」

檢視所有標籤

React 與 Next.js 入門教學筆記 | 學習筆記

· 閱讀時間約 3 分鐘
kdchang

1. React 簡介

React 是由 Facebook(現 Meta)開發的 JavaScript 前端函式庫,主要用於構建 UI 元件。它採用組件化開發方式,並透過 Virtual DOM 提升效能。

2. Next.js 簡介

Next.js 是一個基於 React 的框架,提供伺服器端渲染(SSR)、靜態網站生成(SSG)等功能,讓開發者能夠更輕鬆地開發 SEO 友好的應用程式。

3. 安裝 Next.js

使用 create-next-app 初始化 Next.js 專案:

npx create-next-app@latest my-next-app
cd my-next-app
npm run dev

這將會啟動開發伺服器,預設運行於 http://localhost:3000

4. Next.js 核心概念

4.1 頁面(Pages)

Next.js 使用 pages/ 目錄來定義路由,每個 .js.tsx 文件會自動成為一個頁面。

範例:pages/index.js

export default function Home() {
return <h1>歡迎來到 Next.js!</h1>;
}

新增 pages/about.js

export default function About() {
return <h1>關於我們</h1>;
}

瀏覽 / 會載入 index.js,瀏覽 /about 會載入 about.js

使用 next/link 來建立導航連結:

import Link from "next/link";

export default function Navbar() {
return (
<nav>
<Link href="/">首頁</Link> | <Link href="/about">關於</Link>
</nav>
);
}

4.3 頁面中的 props

Next.js 支援 getServerSideProps(伺服器端渲染)和 getStaticProps(靜態生成)。

伺服器端渲染(SSR)

export async function getServerSideProps() {
const res = await fetch("https://api.example.com/data");
const data = await res.json();
return { props: { data } };
}

export default function Page({ data }) {
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}

靜態生成(SSG)

export async function getStaticProps() {
const res = await fetch("https://api.example.com/data");
const data = await res.json();
return { props: { data } };
}

export default function Page({ data }) {
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}

5. API 路由(API Routes)

pages/api/ 目錄下建立 API 端點。

範例:pages/api/hello.js

export default function handler(req, res) {
res.status(200).json({ message: "Hello, API!" });
}

請求 /api/hello 會返回 JSON 資料。

6. 使用全域狀態管理(React Context)

import { createContext, useContext, useState } from "react";

const ThemeContext = createContext();

export function ThemeProvider({ children }) {
const [theme, setTheme] = useState("light");
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}

export function useTheme() {
return useContext(ThemeContext);
}

_app.js 中使用 Provider:

import { ThemeProvider } from "../context/ThemeContext";

export default function MyApp({ Component, pageProps }) {
return (
<ThemeProvider>
<Component {...pageProps} />
</ThemeProvider>
);
}

在元件中存取狀態:

import { useTheme } from "../context/ThemeContext";

export default function ThemeToggle() {
const { theme, setTheme } = useTheme();
return (
<button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
切換主題(目前:{theme}
</button>
);
}

7. 總結

Next.js 提供了比 React 更豐富的功能,如內建路由、伺服器端渲染(SSR)和 API 路由,適合開發高效能與 SEO 友好的網站。熟悉這些核心概念後,你可以更輕鬆地構建現代化的前端應用程式。

補充:可選搭配技術(實務常見)

技術目的
Tailwind CSS快速開發 UI 樣式
Zustand/Redux全域狀態管理
NextAuth.js登入驗證(支援 Google, GitHub 等)
PrismaORM 操作 MySQL/PostgreSQL
React Query前端資料抓取與快取
shadcn/ui組件庫(Tailwind 美型元件)

React Custom Hooks 入門教學 | w3schools 學習筆記

· 閱讀時間約 4 分鐘
kdchang

前言

在 React 應用程式中,隨著功能的增加與維護需求的提升,我們常常會遇到多個元件需要共享相同邏輯的情況。為了避免重複撰寫相似程式碼、增加維護成本,React 提供了一個優雅的解法:自訂 Hook(Custom Hook)

自 React 16.8 版本開始,我們能使用 Hook 來管理狀態與副作用,而自訂 Hook 則是讓我們可以把邏輯封裝起來,像一般函式一樣在多個元件中重複使用。這篇文章將介紹如何從實際範例中將撰寫資料抓取的邏輯封裝成一個 Custom Hook,並展示其應用方式。


重點摘要

  • React Hook 是一種能在函式元件中使用的功能,讓你能使用狀態(state)與其他 React 功能。
  • Custom Hook 是自訂的 Hook 函式,其命名必須以 use 開頭(例如:useFetch)。
  • Custom Hook 讓我們可以封裝與重複使用邏輯,而不重複撰寫相同的程式碼。
  • 可透過傳入參數來讓 Hook 更具彈性與重用性。
  • 可將如資料抓取(fetching)、表單邏輯、事件監聽等邏輯封裝成 Custom Hook。

實作範例

初始範例:在元件中撰寫抓取邏輯

以下是一個簡單的 React 元件 Home,它在載入時從 JSONPlaceholder 抓取待辦事項(todo)資料,並顯示在頁面上:

// index.js
import { useState, useEffect } from 'react';
import ReactDOM from 'react-dom/client';

const Home = () => {
const [data, setData] = useState(null);

useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/todos')
.then((res) => res.json())
.then((data) => setData(data));
}, []);

return (
<>
{data &&
data.map((item) => {
return <p key={item.id}>{item.title}</p>;
})}
</>
);
};

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Home />);

這段程式碼可以正常運作,但若我們之後在其他元件中也需要類似的資料抓取功能,就會發現邏輯無法重用,這時就是 Custom Hook 派上用場的時候。


抽出邏輯:撰寫 Custom Hook

我們可以將 fetch 的邏輯抽出成一個名為 useFetch 的自訂 Hook,放在 useFetch.js 檔案中:

// useFetch.js
import { useState, useEffect } from 'react';

const useFetch = (url) => {
const [data, setData] = useState(null);

useEffect(() => {
fetch(url)
.then((res) => res.json())
.then((data) => setData(data));
}, [url]);

return [data];
};

export default useFetch;

這個 Hook 會接收一個 URL 作為參數,並返回從該 URL 抓取到的資料。


使用 Custom Hook 的元件

有了 useFetch 之後,我們就可以在 Home 元件中直接使用這個 Hook,如下所示:

// index.js
import ReactDOM from 'react-dom/client';
import useFetch from './useFetch';

const Home = () => {
const [data] = useFetch('https://jsonplaceholder.typicode.com/todos');

return (
<>
{data &&
data.map((item) => {
return <p key={item.id}>{item.title}</p>;
})}
</>
);
};

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Home />);

說明與分析

這個自訂 Hook 的設計有以下幾個特點:

  1. 命名規則:遵守 React 規範,Hook 函式以 use 開頭(此例為 useFetch),才能被 React 正確識別為 Hook。
  2. 封裝邏輯:將 useStateuseEffect 的邏輯封裝在 useFetch 中,不再重複撰寫於元件內。
  3. 提高重用性:只要傳入不同的 URL,就能從其他 API 抓取資料,使用方式彈性且簡潔。
  4. 維護簡便:若日後需要修改資料抓取的邏輯,只需在 useFetch.js 中調整一次,即可影響所有使用該 Hook 的元件。

總結

自訂 Hook 是 React 開發中非常實用的工具,讓我們能夠撰寫模組化、可重複使用且容易維護的邏輯。當你在多個元件中發現重複的邏輯(如資料抓取、訂閱事件、表單處理等),就可以考慮將其封裝成一個 Custom Hook,提升程式碼品質與開發效率。

透過本文介紹的 useFetch 範例,你應該能夠掌握如何將常見的副作用邏輯抽離為一個可重用的 Hook,也歡迎在實務中嘗試建立更多適合自己應用場景的自訂 Hook。


如果你想了解更多關於資料抓取的技術細節,可以進一步學習 JavaScript Fetch API 的用法,並搭配錯誤處理(如 .catch())與 loading 狀態等進行擴充。這將使你的 Custom Hook 更加完善。

參考文件

  1. React Custom Hooks

React Hooks useMemo 入門教學 | w3schools 學習筆記

· 閱讀時間約 3 分鐘
kdchang

前言

在 React 的開發中,我們常會遇到一些運算成本高昂(expensive)的函式,例如複雜的計算或操作大量資料。如果這些運算每次重新渲染都會重新執行,可能會導致效能低落,甚至造成使用者界面的卡頓或延遲。此時,我們可以使用 React 的 useMemo Hook,透過「記憶化」(memoization)技術,避免不必要的重新計算,從而提升效能。

本文將說明 useMemo 的用途、使用時機,並透過實際範例展示其效能優化的實際效果。


重點摘要

  • useMemo 是 React 提供的 Hook,用來「記憶化」一個計算結果,僅在依賴(dependencies)變動時才重新計算。

  • 它的語法為:useMemo(() => { return value }, [dependencies])

  • useMemo 適合用在:

    • 資源密集的計算
    • 必須避免重複執行的運算
  • useMemouseCallback 類似,但:

    • useMemo 回傳的是 記憶化的值
    • useCallback 回傳的是 記憶化的函式
  • 搭配依賴陣列使用,確保只在必要時重新計算


範例一:未使用 useMemo,效能不佳

以下是一個基本範例,展示若每次重新渲染都執行一次昂貴的計算函式 expensiveCalculation,會造成延遲的情形:

import { useState } from 'react';
import ReactDOM from 'react-dom/client';

const App = () => {
const [count, setCount] = useState(0);
const [todos, setTodos] = useState([]);
const calculation = expensiveCalculation(count); // 每次 render 都執行

const increment = () => {
setCount((c) => c + 1);
};
const addTodo = () => {
setTodos((t) => [...t, 'New Todo']);
};

return (
<div>
<div>
<h2>My Todos</h2>
{todos.map((todo, index) => (
<p key={index}>{todo}</p>
))}
<button onClick={addTodo}>Add Todo</button>
</div>
<hr />
<div>
Count: {count}
<button onClick={increment}>+</button>
<h2>Expensive Calculation</h2>
{calculation}
</div>
</div>
);
};

const expensiveCalculation = (num) => {
console.log('Calculating...');
for (let i = 0; i < 1000000000; i++) {
num += 1;
}
return num;
};

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

問題說明:

當我們只是點擊「Add Todo」按鈕,實際上並未更動 count,但昂貴的 expensiveCalculation(count) 卻仍然被重新執行,造成整體效能低落。


範例二:使用 useMemo 優化效能

接下來我們使用 useMemo 來包裝昂貴的計算函式,使其僅在 count 變動時才重新執行:

import { useState, useMemo } from 'react';
import ReactDOM from 'react-dom/client';

const App = () => {
const [count, setCount] = useState(0);
const [todos, setTodos] = useState([]);
const calculation = useMemo(() => expensiveCalculation(count), [count]);

const increment = () => {
setCount((c) => c + 1);
};
const addTodo = () => {
setTodos((t) => [...t, 'New Todo']);
};

return (
<div>
<div>
<h2>My Todos</h2>
{todos.map((todo, index) => (
<p key={index}>{todo}</p>
))}
<button onClick={addTodo}>Add Todo</button>
</div>
<hr />
<div>
Count: {count}
<button onClick={increment}>+</button>
<h2>Expensive Calculation</h2>
{calculation}
</div>
</div>
);
};

const expensiveCalculation = (num) => {
console.log('Calculating...');
for (let i = 0; i < 1000000000; i++) {
num += 1;
}
return num;
};

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

優化結果:

  • 當你按下「Add Todo」時,不再觸發 expensiveCalculation
  • 僅當 count 變動時,才會重新執行昂貴的計算
  • 大幅減少不必要的運算與效能浪費

總結

useMemo 是 React 提供用來優化效能的重要工具,尤其在面對昂貴計算或複雜資料處理時,能有效避免不必要的運算。雖然 useMemo 不該過度使用於簡單運算,但在大型應用中,對於效能的提升非常有幫助。

使用時機建議:

  • 當某個函式或邏輯的執行成本高昂
  • 該邏輯只需在某些依賴值變化時執行一次
  • 搭配 useMemo 可減少重複運算、提升 UI 響應速度

記得:不要為了使用而使用,只有在效能瓶頸或實際需求下,useMemo 才能發揮其最大效益。

參考文件

  1. React Custom Hooks

React Hooks useCallback 入門教學 | w3schools 學習筆記

· 閱讀時間約 3 分鐘
kdchang

前言

在 React 中,每次元件重新渲染時,元件內部定義的函式也會被重新建立。這會導致某些子元件即使其 props 沒有變動,卻仍然會被重新渲染,造成效能浪費。useCallback Hook 是 React 提供的工具,用來記憶(memoize)函式的參考,以避免不必要的重新渲染。它的使用情境與 useMemo 類似,不同之處在於:useMemo 記憶的是「值」,而 useCallback 記憶的是「函式」。


重點摘要

  • useCallback 是 React 提供的 Hook,可回傳一個記憶化的函式
  • 記憶化的概念就像是快取(caching),只在依賴值變更時才重新建立函式。
  • useCallback 可改善效能,避免函式在每次重新渲染時被重新定義。
  • 常與 React.memo 搭配使用,防止不必要的子元件重新渲染。
  • useMemo 類似,但 useMemo 回傳的是「值」,useCallback 回傳的是「函式」。
  • 若函式沒有依賴到外部變數,也可以將依賴陣列設為空陣列([]),表示永遠不重新建立。

問題說明:為何子元件會重渲染?

在下列範例中,即使 todos 陣列沒有變動,點擊 + 按鈕後,子元件 Todos 仍會重新渲染:

// index.js
import { useState } from 'react';
import ReactDOM from 'react-dom/client';
import Todos from './Todos';

const App = () => {
const [count, setCount] = useState(0);
const [todos, setTodos] = useState([]);

const increment = () => {
setCount((c) => c + 1);
};

const addTodo = () => {
setTodos((t) => [...t, 'New Todo']);
};

return (
<>
<Todos todos={todos} addTodo={addTodo} />
<hr />
<div>
Count: {count}
<button onClick={increment}>+</button>
</div>
</>
);
};

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
// Todos.js
import { memo } from 'react';

const Todos = ({ todos, addTodo }) => {
console.log('child render');
return (
<>
<h2>My Todos</h2>
{todos.map((todo, index) => (
<p key={index}>{todo}</p>
))}
<button onClick={addTodo}>Add Todo</button>
</>
);
};

export default memo(Todos);

上述例子中,我們使用了 React.memo 包裹 Todos 元件,理論上當 todos 不變時,它不該重新渲染。然而,當你點擊「+」按鈕後 Todos 仍然會重渲染,原因在於:

參考相等性(Referential Equality)

每次 App 元件重新渲染時,addTodo 函式都會重新建立,導致其記憶位置不同。雖然 Todosprops.todos 沒變,但 props.addTodo 是一個新的函式,因此 React.memo 偵測到 props 有變動,就會導致子元件重新渲染。


解法:使用 useCallback 記憶函式

為了解決上述問題,可以使用 useCallback 記憶 addTodo 函式,讓它只在 todos 改變時才重新定義:

// index.js
import { useState, useCallback } from 'react';
import ReactDOM from 'react-dom/client';
import Todos from './Todos';

const App = () => {
const [count, setCount] = useState(0);
const [todos, setTodos] = useState([]);

const increment = () => {
setCount((c) => c + 1);
};

const addTodo = useCallback(() => {
setTodos((t) => [...t, 'New Todo']);
}, [todos]);

return (
<>
<Todos todos={todos} addTodo={addTodo} />
<hr />
<div>
Count: {count}
<button onClick={increment}>+</button>
</div>
</>
);
};

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

此時,當點擊 + 按鈕時,addTodo 函式不再被重新建立,因此 Todos 元件也不會被不必要地重新渲染。


總結

  • useCallback 是優化 React 應用效能的重要工具,尤其是在元件傳遞函式給子元件時。
  • 若你搭配 React.memo 使用,請務必搭配 useCallback,以避免函式參考不一致而導致的重渲染。
  • 不過也不要過度使用 useCallback,因為它本身也有效能成本,應該根據實際需求與效能瓶頸來使用。

在實務中,對於常被重複渲染、或複雜邏輯包裝的子元件來說,useCallback 能有效避免不必要的重新渲染,是撰寫高效 React 應用程式時不可忽視的工具。

參考文件

  1. React Custom Hooks

React Hooks useReducer 入門教學 | w3schools 學習筆記

· 閱讀時間約 3 分鐘
kdchang

前言

在 React 中,useState 是管理狀態的基礎 Hook,但當應用的狀態邏輯越來越複雜,像是涉及多個欄位更新、交叉依賴或分支邏輯,使用 useState 可能變得難以維護。這時,React 提供的 useReducer Hook 是一個更合適的選擇。

useReducer 的概念與 Redux 類似,它讓你將狀態管理邏輯集中在一個 reducer 函式中,透過 dispatch 發送動作來更新狀態,使邏輯清晰、可維護性高,非常適合用於中型到大型的應用。


重點摘要

  • useReduceruseState 類似,但更適合處理複雜邏輯或多狀態管理。

  • 語法格式為:const [state, dispatch] = useReducer(reducer, initialState)

  • reducer 是一個函式,根據傳入的 action 物件決定如何更新狀態。

  • 所有狀態更新都透過 dispatch(action) 進行。

  • 初始狀態 initialState 通常是物件或陣列。

  • 適合用在:

    • 表單資料管理
    • Todo List 或清單類資料
    • 複雜的元件邏輯

實際範例:使用 useReducer 建立 Todo 狀態管理

以下範例展示如何透過 useReducer 管理一個 Todo List 的完成狀態切換邏輯。

import { useReducer } from 'react';
import ReactDOM from 'react-dom/client';

// 初始狀態:包含兩個代辦事項
const initialTodos = [
{
id: 1,
title: 'Todo 1',
complete: false,
},
{
id: 2,
title: 'Todo 2',
complete: false,
},
];

// reducer 函式:根據 action 決定如何更新狀態
const reducer = (state, action) => {
switch (action.type) {
case 'COMPLETE':
return state.map((todo) => {
if (todo.id === action.id) {
return { ...todo, complete: !todo.complete }; // 切換完成狀態
} else {
return todo;
}
});
default:
return state; // 沒有匹配的 action 則回傳原狀態
}
};

// 元件:顯示代辦事項清單
function Todos() {
const [todos, dispatch] = useReducer(reducer, initialTodos);

// 處理 checkbox 切換
const handleComplete = (todo) => {
dispatch({ type: 'COMPLETE', id: todo.id });
};

return (
<>
{todos.map((todo) => (
<div key={todo.id}>
<label>
<input type="checkbox" checked={todo.complete} onChange={() => handleComplete(todo)} />
{todo.title}
</label>
</div>
))}
</>
);
}

// 渲染應用
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Todos />);

說明

  • initialTodos 是初始狀態,為一個陣列,包含代辦事項的 idtitlecomplete 狀態。
  • reducer 是一個純函式,根據傳入的 action 型別(此處為 "COMPLETE")來更新對應的 todo 狀態。
  • dispatch 是用來觸發 reducer 的函式,只要呼叫 dispatch({ type: "COMPLETE", id: todo.id }),就會依據 id 切換該 todo 的完成狀態。

延伸說明

上述範例僅實作了完成狀態的切換,但實務中 useReducer 更能發揮作用,因為你可以整合所有的 CRUD 操作邏輯於同一個 reducer 裡,例如:

case "ADD":
return [...state, newTodo];
case "DELETE":
return state.filter(todo => todo.id !== action.id);
case "UPDATE":
return state.map(todo => todo.id === action.id ? { ...todo, title: action.title } : todo);

這樣一來,整個應用的狀態更新都統一由 reducer 管理,讓邏輯集中且更容易除錯與擴充。


總結

useReducer 是 React 提供用來處理複雜狀態邏輯的重要工具。當你遇到以下情況時,建議考慮使用 useReducer

  • 多個狀態變數需要統一處理
  • 狀態轉換邏輯複雜且重複
  • 想將狀態管理從元件中抽離以提升可讀性

透過 useReducer,你可以實現更模組化、可維護的應用狀態邏輯,使開發效率更高、錯誤更少。

參考文件

  1. React Custom Hooks

React Hooks useRef 入門教學 | w3schools 學習筆記

· 閱讀時間約 3 分鐘
kdchang

前言

在 React 中,useRef 是一個非常實用的 Hook,它提供了一個方法來在元件重新渲染之間保留值。不同於 useState,當 useRef 的值改變時不會觸發元件重新渲染,這使它成為追蹤狀態變化、儲存 DOM 參考或避免不必要重新渲染的理想選擇。

本文將深入說明 useRef 的三種主要用途:

  1. 避免重新渲染的狀態儲存
  2. 直接操作 DOM 元素
  3. 追蹤先前的狀態值

重點摘要

  • useRef 可用來在元件間保留值而不觸發重新渲染。

  • 常用於:

    • 儲存不可變的值或變數
    • 直接存取 DOM 元素(透過 ref
    • 記錄先前的狀態(例如輸入欄位的歷史值)
  • useRef() 回傳一個包含 .current 屬性的物件。

  • 修改 ref.current 不會導致畫面重繪,因此對效能影響小。


實際範例

範例 1:避免重新渲染的計數器

使用 useRef 追蹤畫面渲染次數(如果用 useState 反而會導致無限循環):

import { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom/client';

function App() {
const [inputValue, setInputValue] = useState('');
const count = useRef(0);

useEffect(() => {
count.current = count.current + 1;
});

return (
<>
<input type="text" value={inputValue} onChange={(e) => setInputValue(e.target.value)} />
<h1>Render Count: {count.current}</h1>
</>
);
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

說明:

  • useRef(0) 初始化 count.current 為 0。
  • 每次渲染後,useEffect 更新 count.current
  • 即使值變了,畫面不會重新渲染,這正是 useRef 的特性。

範例 2:存取 DOM 元素

在 React 中,大部分的 DOM 操作應交由框架管理,但在特定情況下,我們需要手動聚焦、選取或操作元素,這時 useRef 就派上用場。

import { useRef } from 'react';
import ReactDOM from 'react-dom/client';

function App() {
const inputElement = useRef();

const focusInput = () => {
inputElement.current.focus();
};

return (
<>
<input type="text" ref={inputElement} />
<button onClick={focusInput}>Focus Input</button>
</>
);
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

說明:

  • 使用 ref={inputElement} 將 input 元素指向 useRef() 的回傳值。
  • 呼叫 inputElement.current.focus() 來讓輸入框聚焦。

範例 3:追蹤先前的狀態值

如果你想知道某個值在上一次渲染的狀態是什麼,可以使用 useRef 搭配 useEffect 來達成。

import { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom/client';

function App() {
const [inputValue, setInputValue] = useState('');
const previousInputValue = useRef('');

useEffect(() => {
previousInputValue.current = inputValue;
}, [inputValue]);

return (
<>
<input type="text" value={inputValue} onChange={(e) => setInputValue(e.target.value)} />
<h2>Current Value: {inputValue}</h2>
<h2>Previous Value: {previousInputValue.current}</h2>
</>
);
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

說明:

  • useRef 儲存上一個 inputValue
  • 每次 inputValue 改變時,useEffect 都會更新 previousInputValue.current
  • 在畫面上同時顯示目前值與上一個值。

總結

useRef 是 React 中一個簡單卻強大的工具,適用於以下情境:

  • 維持不影響渲染的狀態資料(例如計數器、定時器 ID、外部資料等)
  • 直接操作 DOM 元素(例如設定焦點、自動滾動)
  • 追蹤先前的狀態值或變數(例如表單內容變化)

理解並善用 useRef 可以幫助你更靈活地處理 React 元件中的各種非視覺狀態,讓應用程式更穩定、效能更佳。下次當你不需要觸發重新渲染時,請記得考慮使用 useRef


如果你需要我幫你將此內容轉換為 Markdown、部落格文章格式或簡報稿,也可以告訴我。

參考文件

  1. React Custom Hooks

React Hooks useContext 入門教學 | w3schools 學習筆記

· 閱讀時間約 3 分鐘
kdchang

前言

在 React 應用程式中,當需要在多個巢狀元件之間共享資料時,傳遞 props 是最基本的做法。但當元件層級變深,這樣的資料傳遞會變得繁瑣且難以維護,這種情況被稱為「props drilling」。為了解決這個問題,React 提供了 Context API,搭配 useContext Hook 可以讓你在不需要一層層傳遞 props 的情況下,輕鬆地在深層元件中讀取共享的狀態。


重點摘要

  • React Context 是一種全域狀態管理的工具。
  • 可以搭配 useState 使用,實現跨元件樹狀結構的資料共享。
  • 「prop drilling」是指一層層傳遞 props,容易造成程式碼混亂。
  • 使用 Context 包裝需要共享資料的元件樹,可避免不必要的傳遞。
  • useContext Hook 用於在任意元件中讀取指定 Context 的值。

問題背景與傳統寫法

假設我們有一個使用者名稱 user 的狀態,我們希望在最上層的元件設定這個狀態,並在最底層的第 5 個元件中使用它。傳統的做法會是逐層傳遞:

import { useState } from 'react';
import ReactDOM from 'react-dom/client';

function Component1() {
const [user, setUser] = useState('Jesse Hall');

return (
<>
<h1>{`Hello ${user}!`}</h1>
<Component2 user={user} />
</>
);
}

function Component2({ user }) {
return (
<>
<h1>Component 2</h1>
<Component3 user={user} />
</>
);
}

function Component3({ user }) {
return (
<>
<h1>Component 3</h1>
<Component4 user={user} />
</>
);
}

function Component4({ user }) {
return (
<>
<h1>Component 4</h1>
<Component5 user={user} />
</>
);
}

function Component5({ user }) {
return (
<>
<h1>Component 5</h1>
<h2>{`Hello ${user} again!`}</h2>
</>
);
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Component1 />);

雖然只有第 1 和第 5 個元件需要這個資料,但第 2 到第 4 個元件也被迫傳遞 props,造成不必要的耦合。


解決方案:使用 React Context 與 useContext Hook

步驟一:建立 Context

import { createContext } from 'react';

const UserContext = createContext();

這段程式碼建立了一個新的 Context,用來傳遞 user 的狀態。


步驟二:使用 Provider 包住需要資料的元件樹

function Component1() {
const [user, setUser] = useState('Jesse Hall');

return (
<UserContext.Provider value={user}>
<h1>{`Hello ${user}!`}</h1>
<Component2 />
</UserContext.Provider>
);
}

UserContext.Provider 負責提供資料給子元件。所有被包住的子元件都能透過 useContext 取得 user 的值。


步驟三:在需要的元件中使用 useContext 取得資料

import { useContext } from 'react';

function Component5() {
const user = useContext(UserContext);

return (
<>
<h1>Component 5</h1>
<h2>{`Hello ${user} again!`}</h2>
</>
);
}

只需一行即可取得 Context 的值,避免層層傳遞 props


完整範例程式碼

import { useState, createContext, useContext } from 'react';
import ReactDOM from 'react-dom/client';

// 建立 Context
const UserContext = createContext();

function Component1() {
const [user, setUser] = useState('Jesse Hall');

return (
<UserContext.Provider value={user}>
<h1>{`Hello ${user}!`}</h1>
<Component2 />
</UserContext.Provider>
);
}

function Component2() {
return (
<>
<h1>Component 2</h1>
<Component3 />
</>
);
}

function Component3() {
return (
<>
<h1>Component 3</h1>
<Component4 />
</>
);
}

function Component4() {
return (
<>
<h1>Component 4</h1>
<Component5 />
</>
);
}

function Component5() {
const user = useContext(UserContext);

return (
<>
<h1>Component 5</h1>
<h2>{`Hello ${user} again!`}</h2>
</>
);
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Component1 />);

總結

透過 useContext Hook 搭配 Context API,你可以有效管理全域或區域性的狀態,避免繁瑣的 props 傳遞,讓元件間的溝通更加清晰與高效。這種模式特別適用於需要在多個巢狀元件中共享資料的情境,例如主題切換、登入狀態管理、使用者資料等。

熟練掌握 React Context 與 useContext,將大幅提升你在開發大型 React 應用的能力與維護性。

參考文件

  1. React Custom Hooks

React Hooks useEffect 入門教學 | w3schools 學習筆記

· 閱讀時間約 3 分鐘
kdchang

前言

在 React 函式型元件中,useEffect 是一個強大且常用的 Hook,用來處理副作用(side effects)。副作用指的是那些不直接涉及元件渲染的操作,例如:發送 API 請求、操作 DOM、設定或清除計時器等。

傳統上,這些操作會在 componentDidMountcomponentDidUpdatecomponentWillUnmount 等生命週期函式中進行,而在函式元件中,useEffect 正是用來統一處理這些行為。


重點摘要

  • useEffect 可以執行副作用操作,例如:抓取資料、設定計時器、監聽事件。
  • 語法格式:useEffect(函式, 依賴陣列)
  • 不提供第二個參數時,useEffect 每次重新渲染都會執行。
  • 傳入空陣列作為第二個參數,則只會在元件初次渲染時執行一次。
  • 若依賴陣列中包含特定的 state 或 props,只要它們改變,副作用就會重新執行。
  • 可以在 useEffect 裡透過回傳一個函式進行資源清除(cleanup),避免記憶體洩漏。

實際範例

範例一:沒有依賴陣列,導致每次渲染都執行

import { useState, useEffect } from 'react';
import ReactDOM from 'react-dom/client';

function Timer() {
const [count, setCount] = useState(0);

useEffect(() => {
setTimeout(() => {
setCount((count) => count + 1);
}, 1000);
});

return <h1>I've rendered {count} times!</h1>;
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Timer />);

問題說明:

  • 每次渲染都會重新執行 useEffect,導致 setTimeout 一直重複,數字不斷累加,非預期行為。

範例二:使用空陣列作為依賴,只執行一次

import { useState, useEffect } from 'react';
import ReactDOM from 'react-dom/client';

function Timer() {
const [count, setCount] = useState(0);

useEffect(() => {
setTimeout(() => {
setCount((count) => count + 1);
}, 1000);
}, []); // 加上空陣列,只在初始渲染時執行一次

return <h1>I've rendered {count} times!</h1>;
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Timer />);

重點:

  • 空陣列表示沒有任何依賴,因此只會在元件掛載時執行一次副作用。

範例三:有依賴變數,根據 count 改變而重新執行

import { useState, useEffect } from 'react';
import ReactDOM from 'react-dom/client';

function Counter() {
const [count, setCount] = useState(0);
const [calculation, setCalculation] = useState(0);

useEffect(() => {
setCalculation(() => count * 2);
}, [count]); // 每次 count 改變就重新計算

return (
<>
<p>Count: {count}</p>
<button onClick={() => setCount((c) => c + 1)}>+</button>
<p>Calculation: {calculation}</p>
</>
);
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Counter />);

重點:

  • 依賴陣列中包含 count,因此只要 count 改變,useEffect 就會重新執行,計算新的值。

副作用的清除(Effect Cleanup)

某些副作用,如計時器、訂閱、事件監聽器等,當元件卸載或依賴改變時,應該清除,否則可能會導致記憶體洩漏或非預期行為。

useEffect 中可以回傳一個函式,用來執行清除動作。

範例四:清除計時器

import { useState, useEffect } from 'react';
import ReactDOM from 'react-dom/client';

function Timer() {
const [count, setCount] = useState(0);

useEffect(() => {
const timer = setTimeout(() => {
setCount((count) => count + 1);
}, 1000);

return () => clearTimeout(timer); // 清除 timeout
}, []);

return <h1>I've rendered {count} times!</h1>;
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Timer />);

說明:

  • setTimeout 被命名為 timer,在 useEffect 的清除函式中使用 clearTimeout(timer) 移除它,避免重複執行。

總結

React 的 useEffect 是處理副作用的主要工具。理解它的運作邏輯、依賴機制與清除策略,能幫助開發者更有效率地控制元件的生命周期與效能。

使用建議:

  • 若副作用只需在元件初次渲染執行,請傳入空陣列。
  • 若需要根據變數變動執行副作用,將其加入依賴陣列中。
  • 若副作用產生了外部資源(如計時器、訂閱等),務必記得清除。

掌握這些原則後,就能更靈活並安全地使用 useEffect,打造高效穩定的 React 應用。

參考文件

  1. React Custom Hooks

React Hooks useState 入門教學 | w3schools 學習筆記

· 閱讀時間約 3 分鐘
kdchang

前言

在 React 函式元件中,useState 是最常用的 Hook 之一,能讓我們在無需使用 class 的情況下新增與管理元件的「狀態」(state)。狀態是指會隨著使用者互動或應用邏輯變化而更新的資料,例如:輸入框的內容、按鈕點擊次數、切換的主題顏色等。


重點摘要

  • useState 是 React 提供的 Hook,用來在函式型元件中儲存與更新狀態。
  • 使用前需先從 react 匯入 useState
  • useState(初始值) 會回傳一個陣列,包含目前的狀態值與更新狀態的函式。
  • 更新狀態請使用 setXXX 函式,不可直接修改狀態變數
  • 可以建立多個 useState 追蹤不同變數,也可使用一個物件整合多個欄位。
  • 若要更新物件或陣列的部分內容,應使用展開運算子(spread operator)來保留其他值。

實際範例

1. 匯入 useState

import { useState } from 'react';

使用時,請確認使用的是具名匯入(named import),useStatereact 模組的一部分。


2. 初始化狀態

function FavoriteColor() {
const [color, setColor] = useState('');
}

color 是目前狀態值;setColor 是修改此狀態的函式;初始值設定為空字串。


3. 讀取狀態並渲染

function FavoriteColor() {
const [color, setColor] = useState('red');

return <h1>My favorite color is {color}!</h1>;
}

此例中,畫面上會顯示 My favorite color is red!,透過 JSX 讀取狀態。


4. 使用按鈕更新狀態

function FavoriteColor() {
const [color, setColor] = useState('red');

return (
<>
<h1>My favorite color is {color}!</h1>
<button type="button" onClick={() => setColor('blue')}>
Blue
</button>
</>
);
}

點擊按鈕時,會透過 setColorcolor 更新為 "blue",畫面也會即時更新。


5. 多個狀態變數

function Car() {
const [brand, setBrand] = useState('Ford');
const [model, setModel] = useState('Mustang');
const [year, setYear] = useState('1964');
const [color, setColor] = useState('red');

return (
<>
<h1>My {brand}</h1>
<p>
It is a {color} {model} from {year}.
</p>
</>
);
}

這種方式使用多個 useState 管理多個欄位,彼此獨立。


6. 使用物件作為單一狀態

function Car() {
const [car, setCar] = useState({
brand: 'Ford',
model: 'Mustang',
year: '1964',
color: 'red',
});

return (
<>
<h1>My {car.brand}</h1>
<p>
It is a {car.color} {car.model} from {car.year}.
</p>
</>
);
}

以物件作為狀態,可集中管理多個欄位,也使程式碼更易維護。


7. 更新物件中的單一欄位

function Car() {
const [car, setCar] = useState({
brand: 'Ford',
model: 'Mustang',
year: '1964',
color: 'red',
});

const updateColor = () => {
setCar((prevState) => {
return { ...prevState, color: 'blue' };
});
};

return (
<>
<h1>My {car.brand}</h1>
<p>
It is a {car.color} {car.model} from {car.year}.
</p>
<button type="button" onClick={updateColor}>
Blue
</button>
</>
);
}

這裡使用展開運算子(...prevState)來保留其他屬性,僅更新 color。若直接使用 setCar({ color: "blue" }),會造成其他屬性遺失。


總結

React 的 useState 是建立互動式 UI 的基礎,能讓我們在函式型元件中管理狀態。透過這個 Hook,我們可以:

  • 初始化與讀取狀態值
  • 透過更新函式改變狀態並重新渲染畫面
  • 使用多個 useState 管理多個資料
  • 或整合為一個物件並使用展開運算子更新部分欄位

熟練掌握 useState 是學會 React 開發不可或缺的第一步。建議初學者透過實作各種小範例來加深理解與記憶。

參考文件

  1. React Custom Hooks

React Hooks 入門教學 | w3schools 學習筆記

· 閱讀時間約 2 分鐘
kdchang

前言

Hooks 是在 React 16.8 版本中加入的新功能。Hooks 讓你可以在「函式元件(function components)」中使用狀態(state)以及其他 React 功能。因此,自從有了 Hooks 之後,類別元件(class components)通常就不再是必要的了

儘管 Hooks 幾乎取代了類別元件的使用方式,但 React 團隊目前並沒有打算移除 class 元件的支援。


什麼是 Hook?

Hooks 讓我們能夠「掛勾(hook into)」React 的核心功能,例如 狀態管理(state)生命週期方法(lifecycle methods)


範例:

import React, { useState } from 'react';
import ReactDOM from 'react-dom/client';

function FavoriteColor() {
const [color, setColor] = useState('red');

return (
<>
<h1>我最喜歡的顏色是 {color}</h1>
<button type="button" onClick={() => setColor('blue')}>
藍色
</button>
<button type="button" onClick={() => setColor('red')}>
紅色
</button>
<button type="button" onClick={() => setColor('pink')}>
粉紅色
</button>
<button type="button" onClick={() => setColor('green')}>
綠色
</button>
</>
);
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<FavoriteColor />);

在這個範例中,我們透過 useState 這個 Hook 來追蹤應用程式的狀態。

狀態(State):泛指那些會隨著使用者互動或應用邏輯而改變的資料或屬性。

此外,使用 Hook 前必須先從 react 模組中引入對應的函式,例如這裡的 useState


使用 Hook 的三大規則

  1. 只能在 React 的函式元件中呼叫 Hooks
  2. 只能在元件的最上層(Top-level)呼叫,不能寫在 iffor函式中等區塊內
  3. 不能有條件式地呼叫 Hook(例如不能寫在 if 判斷中)

注意:Hooks 無法在 class 類別元件中使用!


自訂 Hook(Custom Hooks)

如果你有一些包含狀態的邏輯(stateful logic),需要在多個元件之間重複使用,這時可以考慮封裝成自訂 Hook,以提升可讀性與重用性。

參考文件

  1. React Custom Hooks