Next.js 12转13代码适配问题:无法使用useState及请求异常
Next.js 12 升级到13后 useState 错误的解决方法
错误原因
Next.js 13 默认启用 App Router,其中组件默认是服务器组件,而 useState 这类 React 客户端 Hooks 只能在客户端组件中使用。如果你的代码是 Pages Router 结构(比如当前的 pages/index.js),原本应该兼容,但如果是迁移到 App Router 或者依赖版本不匹配,就会触发这个错误。
方案一:继续使用 Pages Router(最小改动)
Next.js 13 保留了 Pages Router,你的原有代码属于这个结构,只需做以下检查:
- 确认页面文件放在项目根目录的
pages文件夹下(比如当前的pages/index.js) - 确保
react和react-dom版本升级到 18+(Next.js 13 的强制要求) - 检查
next.config.js中没有强制启用 App Router 的配置(默认无需额外配置)
这种情况下,你的原有代码几乎不需要修改就能正常运行,若仍报错,可尝试删除 node_modules 和 .next 文件夹后重新安装依赖。
方案二:迁移到 App Router(推荐)
如果要切换到 Next.js 13 的新路由体系,需要做以下改造:
1. 标记客户端组件
在使用 Hooks 的组件顶部添加 'use client' 指令,告诉 Next.js 这是客户端组件:
'use client'; // 必须放在代码最顶部 import { useState } from 'react'; // 其他导入语句...
2. 拆分异步数据获取与客户端状态
App Router 中,异步组件不能同时是客户端组件,所以需要把数据获取和状态逻辑拆分成两部分:服务器组件负责获取初始数据,客户端子组件处理状态和交互。
改造后的完整代码
// app/page.js(App Router 的首页文件) import Head from 'next/head'; import { loadData } from './api/post'; import { Section, Cover, SocialNetworks, BuyMeCoffee } from '../components'; import PostSection from './PostSection'; const LOAD_MORE_STEP = 4; // 服务器组件:异步获取初始数据 export default async function Home() { const { posts, total } = await loadData(0, LOAD_MORE_STEP); return ( <div style={{ marginBottom: '1rem' }}> <Head> <title>My blog</title> </Head> <Section> <Cover title="Elena<br />Litvinova" /> <SocialNetworks /> <BuyMeCoffee /> </Section> <PostSection initialPosts={posts} total={total} /> </div> ) }
// app/PostSection.js(客户端子组件) 'use client'; import { useState } from 'react'; import { Section, Title, PostGrid, Post, Button } from '../components'; const LOAD_MORE_STEP = 4; export default function PostSection({ initialPosts, total }) { const [posts, setPosts] = useState(initialPosts); const [loadedAmount, setLoadedAmount] = useState(LOAD_MORE_STEP); const [loading, setLoading] = useState(false); const showLoadButton = total > loadedAmount; const getMorePosts = async () => { setLoading(true); try { const res = await fetch(`/api/post?start=${loadedAmount}&end=${loadedAmount + LOAD_MORE_STEP}`); const data = await res.json(); // 使用函数式更新避免闭包问题 setLoadedAmount(prev => prev + LOAD_MORE_STEP); setPosts(prev => [...prev, ...data.posts]); setLoading(false); } catch (error) { console.error(error); setLoading(false); } }; return ( <Section> <Title>New Post</Title> <PostGrid> {posts.map((post) => ( <Post key={post._id} {...post} /> ))} </PostGrid> {showLoadButton && ( <div style={{ display: 'flex', justifyContent: 'center' }}> <Button disabled={loading} onClick={getMorePosts}> {loading ? '加载中...' : '加载更多文章...'} </Button> </div> )} </Section> ); }
额外优化
- 使用函数式更新状态(
setLoadedAmount(prev => prev + LOAD_MORE_STEP)),避免闭包导致的状态更新错误 - 优化按钮的加载状态文本,提升用户体验
内容的提问来源于stack exchange,提问作者Galanthus
相关产品推荐
相关产品推荐

