React use() Hook
use 是一個特殊的 Hook,讓你可以讀取 Promise 或 Context 的值。它是 React 中用來處理非同步資料的現代方式。
基本語法
import { use } from 'react'
const value = use(resource)
resource 可以是:
- 一個 Promise
- 一個 Context
讀取 Promise
use 可以讓你在元件中「等待」Promise 完成並取得結果:
import { use, Suspense } from 'react'
// 建立一個 Promise(通常是 API 呼叫)
async function fetchUser(id) {
const response = await fetch(`/api/users/${id}`)
return response.json()
}
function UserProfile({ userPromise }) {
// use 會等待 Promise 完成
const user = use(userPromise)
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
)
}
function App() {
// 在父元件建立 Promise
const userPromise = fetchUser(1)
return (
// 使用 Suspense 顯示載入狀態
<Suspense fallback={<p>載入中...</p>}>
<UserProfile userPromise={userPromise} />
</Suspense>
)
}
使用
use 讀取 Promise 時,必須搭配 Suspense 元件來處理載入狀態。use vs useEffect + useState
傳統方式使用 useEffect 和 useState:
// 傳統方式
function UserProfile({ userId }) {
const [user, setUser] = useState(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
setLoading(true)
fetch(`/api/users/${userId}`)
.then((res) => res.json())
.then((data) => {
setUser(data)
setLoading(false)
})
}, [userId])
if (loading) return <p>載入中...</p>
return <h1>{user.name}</h1>
}
使用 use 的方式更簡潔:
// 使用 use
function UserProfile({ userPromise }) {
const user = use(userPromise)
return <h1>{user.name}</h1>
}
// 父元件
function App() {
const userPromise = fetchUser(1)
return (
<Suspense fallback={<p>載入中...</p>}>
<UserProfile userPromise={userPromise} />
</Suspense>
)
}
快取 Promise
重要的是,傳給 use 的 Promise 應該要被快取,避免每次渲染都建立新的 Promise:
// ❌ 不好:每次渲染都建立新的 Promise
function UserProfile({ userId }) {
const user = use(fetchUser(userId)) // 每次渲染都會觸發新請求
return <h1>{user.name}</h1>
}
// ✅ 好:在父元件建立 Promise,或使用快取機制
function App() {
const userPromise = useMemo(() => fetchUser(userId), [userId])
return (
<Suspense fallback={<p>載入中...</p>}>
<UserProfile userPromise={userPromise} />
</Suspense>
)
}
實務上,通常會使用資料獲取函式庫(如 TanStack Query、SWR)或框架提供的資料載入機制來處理快取。
讀取 Context
use 也可以用來讀取 Context,和 useContext 功能相同:
import { use, createContext } from 'react'
const ThemeContext = createContext('light')
function ThemedButton() {
const theme = use(ThemeContext)
return <button className={theme}>{theme} 主題按鈕</button>
}
use vs useContext
兩者的差別在於 use 可以在條件判斷中使用:
// useContext 不能在條件中使用
function Component({ showTheme }) {
// ❌ 錯誤:Hook 不能在條件中呼叫
// if (showTheme) {
// const theme = useContext(ThemeContext);
// }
}
// use 可以在條件中使用
function Component({ showTheme }) {
// ✅ 正確:use 可以在條件中呼叫
if (showTheme) {
const theme = use(ThemeContext)
return <p>主題:{theme}</p>
}
return <p>不顯示主題</p>
}
在條件和迴圈中使用 use
use 的特殊之處在於它可以在條件判斷、迴圈、甚至提前返回之後呼叫:
function MessageComponent({ messagePromise, shouldShowMessage }) {
// 在條件判斷後使用
if (!shouldShowMessage) {
return null
}
// 這在傳統 Hook 中是不允許的,但 use 可以
const message = use(messagePromise)
return <p>{message}</p>
}
搭配錯誤邊界
當 Promise 被拒絕(rejected)時,可以使用 Error Boundary 來處理錯誤:
import { use, Suspense } from 'react'
import { ErrorBoundary } from 'react-error-boundary'
function UserProfile({ userPromise }) {
const user = use(userPromise)
return <h1>{user.name}</h1>
}
function App() {
const userPromise = fetchUser(1)
return (
<ErrorBoundary fallback={<p>發生錯誤,請重試</p>}>
<Suspense fallback={<p>載入中...</p>}>
<UserProfile userPromise={userPromise} />
</Suspense>
</ErrorBoundary>
)
}
實際範例:載入多個資源
import { use, Suspense } from 'react'
// API 函式
async function fetchUser(id) {
const res = await fetch(`/api/users/${id}`)
if (!res.ok) throw new Error('Failed to fetch user')
return res.json()
}
async function fetchPosts(userId) {
const res = await fetch(`/api/users/${userId}/posts`)
if (!res.ok) throw new Error('Failed to fetch posts')
return res.json()
}
// 使用者資訊元件
function UserInfo({ userPromise }) {
const user = use(userPromise)
return (
<div className="user-info">
<img src={user.avatar} alt={user.name} />
<h1>{user.name}</h1>
<p>{user.bio}</p>
</div>
)
}
// 文章列表元件
function UserPosts({ postsPromise }) {
const posts = use(postsPromise)
return (
<ul className="posts">
{posts.map((post) => (
<li key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</li>
))}
</ul>
)
}
// 主要元件
function UserPage({ userId }) {
// 建立 Promise(會平行載入)
const userPromise = fetchUser(userId)
const postsPromise = fetchPosts(userId)
return (
<div className="user-page">
{/* 使用者資訊先載入完成就先顯示 */}
<Suspense fallback={<p>載入使用者資訊...</p>}>
<UserInfo userPromise={userPromise} />
</Suspense>
{/* 文章可以獨立載入 */}
<Suspense fallback={<p>載入文章...</p>}>
<UserPosts postsPromise={postsPromise} />
</Suspense>
</div>
)
}
注意事項
- Promise 需要快取:避免在渲染中建立新的 Promise
- 需要搭配 Suspense:用來顯示載入狀態
- 建議搭配 Error Boundary:用來處理錯誤狀態
- use 可以條件呼叫:這是它和其他 Hook 的主要區別