React Query调用refetch时携带旧state参数发起请求的问题
问题原因
- 本质是React闭包陷阱 + Hook执行顺序问题:
- React组件重渲染时,所有Hook会严格按照代码书写顺序从上到下执行。当
body状态更新触发重渲染时,写在前面的useEffect会先执行,此时它拿到的refetch是上一次渲染周期中useQuery返回的实例,对应的查询函数()=>fetchCommissionsData(body)捕获的是上一轮渲染的旧body值。 useEffect同步逻辑执行完成后,代码才会走到后面的useQuery调用,生成捕获了最新body值的新查询函数和新refetch方法,但此时旧refetch已经带着旧参数发起了请求。
- React组件重渲染时,所有Hook会严格按照代码书写顺序从上到下执行。当
- 该写法本身不符合React Query的设计逻辑:查询依赖的参数应当放入queryKey中,由React Query自动监听key变化触发重查,额外维护本地状态+
useEffect手动调用refetch的写法绕开了框架的依赖追踪机制,极易触发闭包问题。
修复方案
推荐写法(符合React Query设计规范)
去掉多余的本地body状态和手动refetch逻辑,将请求参数纳入queryKey管理,依靠React Query原生的依赖追踪自动触发查询,从根源避免闭包问题:
import React from 'react' import { getCommissionsData } from "../utils/apis"; import { queryKeys } from "../utils/constants"; import { useQueryClient, useQuery } from "react-query"; const fetchCommissionsData = async (request) => { const response = await getCommissionsData(request) return response } export function useCommissionsData() { const queryClient = useQueryClient() const searchValues = queryClient.getQueryData(queryKeys.searchValues) // 对外暴露的查询方法,拼接完参数后更新缓存中的请求体 const getCommissionsData = (data) => { const requestBody = { ...data, movementType: searchValues.movementType, viewType: searchValues.viewType, summaryType: searchValues.summaryType } queryClient.setQueryData([queryKeys.commissionsData, 'requestBody'], requestBody) } // 从缓存读取当前最新的请求参数 const currentBody = queryClient.getQueryData([queryKeys.commissionsData, 'requestBody']) ?? null const fallback = []; // 将请求参数放入queryKey,参数变化时自动触发查询 const { data = fallback } = useQuery( [queryKeys.commissionsData, currentBody], () => fetchCommissionsData(currentBody), { refetchOnWindowFocus: false, enabled: !!currentBody // 初始无参数时不发起请求 } ); return { data, getCommissionsData } }
兼容原有逻辑的最小改动写法
如果要保留本地维护body状态的逻辑,只需要调整Hook书写顺序:把useQuery移到useEffect之前,保证useEffect执行时能拿到最新的refetch实例,同时补全依赖数组即可:
export function useCommissionsData() { const [body, setBody] = useState(null) const queryClient = useQueryClient() const searchValues = queryClient.getQueryData(queryKeys.searchValues) const getCommissionsData = (data) => { const newBody = { ...data, movementType: searchValues.movementType, viewType: searchValues.viewType, summaryType: searchValues.summaryType } setBody(newBody) } const fallback = []; // 先执行useQuery,生成绑定了最新body值的查询函数和refetch const { data = fallback, refetch } = useQuery([queryKeys.commissionsData], () => fetchCommissionsData(body), { refetchOnWindowFocus: false, enabled: false }); // useEffect写在useQuery之后,拿到的是最新的refetch useEffect(() => { if(body) refetch() }, [body, refetch]) return { data, getCommissionsData } }
内容的提问来源于stack exchange,提问作者Leon
相关产品推荐
相关产品推荐

