跳至內容
文件錯誤非同步用戶端元件錯誤

非同步用戶端元件錯誤

用戶端元件不能是非同步函式。

錯誤原因

當您嘗試將用戶端元件定義為非同步函式時,就會發生此錯誤。React 用戶端元件不支援非同步函式。例如:

'use client'
 
// This will cause an error
async function ClientComponent() {
  // ...
}

可能的解決方法

  1. 轉換為伺服器元件:如果可以,請將您的客戶端元件轉換為伺服器元件。這讓您可以直接在元件中使用 async/await
  2. 移除 async 關鍵字:如果您需要將其保留為客戶端元件,請移除 async 關鍵字並以其他方式處理資料擷取。
  3. 使用 React hooks 進行資料擷取:利用 useEffect 等 hooks 進行客戶端資料擷取,或使用第三方函式庫。
  4. 使用 Context Provider 搭配 use hook:使用 context 將 promises 傳遞給子元件,然後使用 use hook 解析它們。

我們建議在伺服器上擷取資料。例如:

app/page.tsx
export default async function Page() {
  let data = await fetch('https://api.vercel.app/blog')
  let posts = await data.json()
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}

使用 Context Provider 搭配 use

另一種可以探索的模式是使用 React 的 use hook 搭配 Context Provider。這讓您可以將 Promise 傳遞給子元件,並使用 use hook 解析它們。以下是一個範例:

首先,我們為 context provider 建立一個單獨的檔案:

app/context.tsx
'use client'
 
import { createContext, useContext } from 'react'
 
export const BlogContext = createContext<Promise<any> | null>(null)
 
export function BlogProvider({
  children,
  blogPromise,
}: {
  children: React.ReactNode
  blogPromise: Promise<any>
}) {
  return (
    <BlogContext.Provider value={blogPromise}>{children}</BlogContext.Provider>
  )
}
 
export function useBlogContext() {
  let context = useContext(BlogContext)
  if (!context) {
    throw new Error('useBlogContext must be used within a BlogProvider')
  }
  return context
}

現在,我們在伺服器元件中建立 Promise 並將其串流至客戶端:

app/page.tsx
import { BlogProvider } from './context'
 
export default function Page() {
  let blogPromise = fetch('https://api.vercel.app/blog').then((res) =>
    res.json()
  )
 
  return (
    <BlogProvider blogPromise={blogPromise}>
      <BlogPosts />
    </BlogProvider>
  )
}

這是部落格文章元件:

app/blog-posts.tsx
'use client'
 
import { use } from 'react'
import { useBlogContext } from './context'
 
export function BlogPosts() {
  let blogPromise = useBlogContext()
  let posts = use(blogPromise)
 
  return <div>{posts.length} blog posts</div>
}

這種模式允許您及早開始資料擷取,並將 Promise 向下傳遞給子元件,子元件然後可以使用 use hook 在資料準備就緒時存取資料。

客戶端資料擷取

在需要客戶端擷取的情況下,您可以在 useEffect 中呼叫 fetch(不建議),或使用社群中熱門的 React 函式庫(例如 SWRReact Query)進行客戶端擷取。

app/page.tsx
'use client'
 
import { useState, useEffect } from 'react'
 
export function Posts() {
  let [posts, setPosts] = useState(null)
 
  useEffect(() => {
    async function fetchPosts() {
      let res = await fetch('https://api.vercel.app/blog')
      let data = await res.json()
      setPosts(data)
    }
    fetchPosts()
  }, [])
 
  if (!posts) return <div>Loading...</div>
 
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}