React中useState接收服务器返回JSON对象时出现Hook数量不符报错的问题
问题根本原因
这个报错和setUser传入的参数结构没有直接关系,本质是违反React Hooks调用规则导致的连锁反应,你观察到的「包裹data就不报错」只是巧合的表象:
- React Hooks的强制规则要求:所有hooks(useState、useEffect等)必须在组件每次渲染时,以完全一致的顺序、在组件顶层无差别调用,绝对不能放在条件判断、循环、提前return的代码分支内。
- 你当前的代码把拉取会话的
useEffect写在了if (!user.session_present)的条件分支内部:- 组件首次渲染时,初始user对象没有
session_present属性,条件判断为真,分支内部的useEffect会被调用。 - 当你直接把接口返回的
res.data传入setUser时,更新后的user对象会包含session_present属性,第二次渲染时条件判断为假,分支内部的useEffect不会执行——此时组件本次渲染的hooks调用数比上一次少了一个,直接触发「Rendered fewer hooks than expected」的报错。 - 当你把参数包裹为
{data: res.data}传入setUser时,更新后的user对象结构是{原基础属性, data: {带session_present的对象}},此时判断user.session_present是否存在依然为假,条件分支每次渲染都会执行,对应的useEffect每次都会被调用,hooks调用数和之前一致,自然不会触发报错,但这时候状态被嵌套在了data属性下,和你的预期不符。
- 组件首次渲染时,初始user对象没有
- 这个现象完全和setUser的参数兼容性无关:setUser原生支持直接传入接口返回的原始对象作为状态更新值,不存在传原始对象就必须报错的问题。
正确实现方式
- 核心调整:把所有hooks移到组件顶层,绝对不能把useEffect、useState等hooks放在条件判断分支内部,保证每次渲染时hooks的调用顺序、调用数量完全一致。
- 把原有的“判断分支内调用useEffect”的逻辑,调整为“useEffect内部做条件判断”:
// 组件顶层初始化状态 const [user, setUser] = useState({ authenticated: false, user_id: '', user_email: '' }) // 顶层调用useEffect,不嵌套在任何条件分支里 useEffect(() => { // 在effect内部做会话存在性判断,不需要把useEffect本身放进分支 if (user.session_present) return axios.get('/api/auth/session/').then(res => { // 直接传入接口返回的原始对象,不需要额外包裹 // 如果担心覆盖原有属性,可以用展开运算符做浅合并 setUser(prev => ({ ...prev, ...res.data })) }) }, [user.session_present]) // 依赖项监听会话标识字段,避免不必要的重复请求 - 调整完成后,你既可以直接使用服务器返回的原始结构更新状态,不会出现data属性的嵌套问题,也不会再触发hooks数量不匹配的运行错误。
内容的提问来源于stack exchange,提问作者cphill
相关产品推荐
相关产品推荐

