Next.js+Sanity使用'use client'时params解构错误及解决方案咨询
问题分析与解决方案
核心结论
加'use client'后必须通过useEffect(或数据获取库如SWR/React Query)调用getProducts,不能像服务器组件那样直接在组件顶层用async/await。
错误原因
- 客户端组件不支持顶层
async/await:Next.js中,async组件语法仅适用于服务器组件(无'use client'指令)。加了'use client'后,组件不能定义为async函数,否则会导致props传递异常,出现params未定义的错误。 - 客户端组件无法直接接收
params作为props:服务器组件会自动从路由中获取params并传入props,但客户端组件需要通过useParams钩子来获取路由参数。
解决步骤
1. 调整组件结构,使用客户端钩子
- 移除组件的
async关键字 - 用
useParams获取路由中的productslug - 用
useState存储产品数据和加载状态 - 用
useEffect触发数据获取
2. 修正数据渲染逻辑
你的getProducts返回的是数组(因为GROQ查询用了[]),而原代码存在渲染错误(比如products.name,应该是products[0]?.name),需要调整渲染逻辑适配数组结构。
修改后的完整代码
'use client' import { useEffect, useState } from 'react' import { useParams } from 'next/navigation' import { getProducts } from '@/sanity/getProduct' import { Product } from '@/app/components' import { AiFillStar, AiOutlineMinus, AiOutlinePlus } from 'react-icons/ai' const ProductPage = () => { const params = useParams() const slug = params.product const [products, setProducts] = useState([]) const [loading, setLoading] = useState(true) useEffect(() => { const fetchProduct = async () => { if (!slug) return const data = await getProducts(slug) setProducts(data) setLoading(false) } fetchProduct() }, [slug]) if (loading) { return <div>Loading...</div> } // 因为查询的是单个slug的产品,取数组第一个元素 const product = products[0] if (!product) { return <div>Product not found</div> } return ( <> <div> <div className='flex flex-col justify-center items-center md:flex-row md:justify-around'> <div className='flex flex-col'> <div className='flex flex-col'> {product.images && product.images.length > 0 && ( <img src={product.images[0].url} height={300} width={300} alt={product.images[0].alt || product.name} /> )} <h2>{product.name}</h2> </div> <div className='flex flex-row'> {product.images && product.images.map((image) => ( <img key={image.url} src={image.url} alt={image.alt || product.name} height={70} width={70} /> ))} </div> </div> <div className='flex flex-col'> <h2>{product.name}</h2> <div className='reviews'> <AiFillStar /> <AiFillStar /> <AiFillStar /> <AiFillStar /> </div> <h4>Details:</h4> <h3>{product.details}</h3> <p className='price'> ${product.price}</p> <div className='quantity-box border max-w-[100px] border-gray-300 rounded flex items-center'> <button className='minus-icon border-r-2 bg-transparent text-lg px-2 py-2'> <AiOutlineMinus /> </button> <span className='quantity px-2'>4</span> <button className='plus-icon border-l-2 bg-transparent text-lg px-2 py-2'> <AiOutlinePlus /> </button> </div> <div className='buttons'> <button type='button' className='add-to-cart'> Add to Cart </button> <button type='button' className='buy-now'> Buy Now </button> </div> </div> </div> <div className='maylike-products-wrapper'> <h2>You may also like</h2> <div className='flex justify-center items-center'> <div className=''> <Product /> </div> </div> </div> </div> </> ) } export default ProductPage
额外优化建议
- 可以用SWR或React Query替代
useEffect,它们自带缓存、重新验证等功能,更适合客户端数据获取 - 给图片添加
alt属性,提升可访问性 - 增加错误处理逻辑(比如捕获
getProducts的异常)
内容的提问来源于stack exchange,提问作者Antony Ponson
相关产品推荐
相关产品推荐

