作者:吳祐賓
React 應用程式開發過程中,很常需要去 Server 端拉資料回來,通常我們會使用 useState, useEffect 來完成工作。
底下是一個簡單的範例,從 JSONPlaceholder (著名的公開偽資料 API 服務)提供的 API 取得使用者資料。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | import React, { useState, useEffect } from 'https://esm.sh/react@18'; // Changed to React 18 for compatibility with useEffect/useState import ReactDOMClient from 'https://esm.sh/react-dom@18/client'; // Changed to React 18 const Users = () => { const [users, setUsers] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); // Added error handling useEffect(() => { const fetchUsers = async () => { const res = await fetch('https://jsonplaceholder.typicode.com/users'); const data = await res.json(); setUsers(data); setLoading(false); }; fetchUsers(); }, []); // Empty dependency array ensures this runs only once on mount if (loading) { return <h1>Loading...</h1>; } if (error) { return <h1>Error: {error.message}</h1>; // Display the error message } return ( <ul> {users.map((user) => ( <div key={user.id}> <h2>{user.name}</h2> </div> ))} </ul> ); }; function App() { return ( // Suspense is not needed with useEffect + useState for initial data loading <Users /> ); } const root = ReactDOMClient.createRoot(document.getElementById('root')); root.render(<App />); |
useEffect + fetch 坊間有很多教學,因為很多人會遇到 "Warning"
這個設計法已經行之有年,還是有很多人搞不清楚 useEffect + fetch 為什麼會出現類似 "Warning: useEffect function must return a cleanup function or nothing. Promises..." 的訊息。
解決方式很多,React 19 也提出了一個解決方式。
use API
use API 是 React 19 提出的新概念,是搭配 React Server Components 框架,如 Next.js 等設計使用。
use 的設計目標是:
- 簡化 Server Components 中的資料取得設計
- 更簡潔的程式碼和更高的可讀性
- 錯誤處理更方便 (Error Boundaries 整合)
直接將 React 18 程式碼升級到 React 19,參考 How to fetch an API with REACT 19 | use - suspense 文章,程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | import React, { Suspense, use } from 'https://esm.sh/react@19'; import ReactDOMClient from 'https://esm.sh/react-dom@19/client'; const fetchUsers = async () => { const res = await fetch('https://jsonplaceholder.typicode.com/users'); return res.json(); }; const Users = () => { const users = use(fetchUsers()); return ( <ul> {users.map((user) => ( <div key={user.id}> <h2>{user.name}</h2> </div> ))} </ul> ); }; function App() { return ( <Suspense fallback={<h1>Loading...</h1>}> <Users /> </Suspense> ); } const root = ReactDOMClient.createRoot(document.getElementById('root')); root.render(<App />); |
程式碼就是如此簡單,但 React Client Components 時會無限 Promise
React Stand-alone 就是 RCC 模式,在這個模式下使用 use API,就會出現如下圖般無限 Promise 的情形。
因為 use 是搭配 RSC 的設計,所以和 render 會有關係,RSC 框架會管理好 use 裡 Promise 的狀態,但 RCC 沒有框架處理 Promise 狀態,流程上是:
User render -> use(new Promise) <in use> -> do Promise -> retrun New Promise <in use> -> "use" call User render... repeat
RCC 的 Promise 管理快取機制自己寫,就為了 use API
這部份和 useCallback 有點像,只有 callback 相依的變數修改了才會更新。Promise 也可以如法泡製。參考 New React 19 use hook–deep dive 的教學,寫了 useQuery 來模擬 Server 端對 Promise 快取機制的處理。
程式碼修改如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | import React, { Suspense, use, useState } from 'https://esm.sh/react@19'; import ReactDOMClient from 'https://esm.sh/react-dom@19/client'; /** useQuery Hook */ const promiseCache = new Map(); const useQuery = ({ fn, key }) => { if (!promiseCache.has(key)) { promiseCache.set(key, fn()); } const promise = promiseCache.get(key); const result = use(promise); return result; }; /* end useQuery Hook */ const fetchUsers = () => { const res = fetch('https://jsonplaceholder.typicode.com/users') .then( response => response.json()); return res; }; const Users = () => { const users = useQuery({fn: ()=> fetchUsers(), key: "fetchUsers"}); return ( <ul> {users.map((user) => ( <div key={user.id}> <h2>{user.name}</h2> </div> ))} </ul> ); }; function App() { return ( <Suspense fallback={<h1>Loading...</h1>}> <Users /> </Suspense> ); } const root = ReactDOMClient.createRoot(document.getElementById('root')); root.render(<App />); |
重點總結:
傳統方式 (React 18 及之前): 使用 useState 和 useEffect 組合來處理資料獲取、載入狀態和錯誤處理。 這是常見且有效的方法,但程式碼相對較多。
React 19 的 use API:
- 設計目標: 簡化資料獲取,特別是在 Server Components (RSC) 環境下,提供更簡潔的程式碼和內建的錯誤處理。
- RSC 環境: use hook 旨在與 React Server Components 框架 (如 Next.js) 搭配使用。 框架會負責管理 Promise 的狀態,避免無限迴圈。
- RCC (React Client Components) 環境限制: 在獨立的 React Client Components (沒有 RSC 框架) 中直接使用 use 會導致無限迴圈,因為沒有機制來管理 Promise 的狀態。
模擬 RSC 的 Promise 快取 (RCC 環境):
- 為了在 RCC 環境下也能體驗 use 的簡潔性,可以自行建立一個 Promise 快取機制 (例如範例中的 useQuery hook)。
- useQuery hook 使用 Map 來儲存 Promise,確保相同的請求只會發送一次,避免重複的非同步操作。
總之,use API 是 React 19 中一個強大的新特性,但目前主要適用於 Server Components 環境。對於傳統的 Client Components,就繼續使用 useEffect 和 useState,對使用新技術有興趣的人,可以自行實作 Promise 快取機制來模擬 use 的行為。
如果你正在使用 Next.js 這樣的 RSC 框架,use API 可以大幅簡化你的資料 request 的程式碼。
和你分享。
See Also
沒有留言:
張貼留言