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

傳統方式使用 useEffectuseState

// 傳統方式
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>
  )
}

注意事項

  1. Promise 需要快取:避免在渲染中建立新的 Promise
  2. 需要搭配 Suspense:用來顯示載入狀態
  3. 建議搭配 Error Boundary:用來處理錯誤狀態
  4. use 可以條件呼叫:這是它和其他 Hook 的主要區別