跳到主要內容
文件錯誤沒有非同步客戶端元件

沒有非同步客戶端元件

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

為何發生此錯誤

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

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

可能的修正方式

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

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

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

搭配 Context Provider 使用 use

另一種值得探索的模式是搭配 Context Provider 使用 React use Hook。這可讓您將 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() {
  const 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() {
  const 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() {
  const blogPromise = useBlogContext()
  const 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() {
  const [posts, setPosts] = useState(null)
 
  useEffect(() => {
    async function fetchPosts() {
      const res = await fetch('https://api.vercel.app/blog')
      const 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>
  )
}