忘掉"发请求",声明你要的数据:TanStack Query 带来的思维革命

声明你要的数据

使用 TanStack Query 后,你不再需要"发请求",你只是在"声明数据"。

更重要的是:你不再需要"管理状态",只需要"消费数据"。

这是一次思维范式的根本转变:

  • 传统方式:你在管理"请求的过程"和"状态的生命周期" - 什么时候发、怎么发、失败了怎么办、要不要重试、loading/error/data 怎么更新
  • TanStack Query:你在声明"数据的需求" - 我需要这个数据,剩下的自动处理

具体改变:

传统方式 TanStack Query
100 行代码管理一个请求 20 行代码实现更多功能
手动定义 useState 管理 loading、error、data 自动提供完整状态,无需手动管理
每个组件都要写 loading/error 逻辑 状态自动同步,写一次到处用
多组件重复发请求 自动去重和共享缓存
手写定时刷新逻辑 配置即可自动更新
复杂的乐观更新和回滚 内置乐观更新机制
需要 Redux/Context 管理服务器数据 内置服务器状态管理

结果:代码量减少 80%,状态管理复杂度降低 90%,但功能更强大、更可靠。

你从"请求的搬运工"变成"数据的消费者",可以将更多精力专注于构建出色的用户体验。


传统开发的困境:状态管理的噩梦

当我们谈论前端数据获取时,脑海中总是浮现这样的画面:

scss 复制代码
// 组件挂载时发请求
function UserProfile() {
  const [user, setUser] = useState(null)
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState(null)
  
  useEffect(() => {
    setLoading(true)
    setError(null)
    
    fetch('/api/user')
      .then(res => res.json())
      .then(data => {
        setUser(data)
        setLoading(false)
      })
      .catch(err => {
        setError(err)
        setLoading(false)
      })
  }, [])
  
  if (loading) return <div>Loading...</div>
  if (error) return <div>Error: {error.message}</div>
  
  return <div>{user?.name}</div>
}

这里有两个根深蒂固的思维模式:

  1. 我们在"发请求"
  2. 我们在"管理状态" - loading、error、data 三个状态要手动定义、手动更新、手动同步

每个请求都需要:

  • 定义 3 个 useState
  • 在请求前后更新这些状态
  • 处理各种边界情况(取消请求、组件卸载、竞态条件...)
  • 在 JSX 里写一堆条件渲染

一个简单的数据获取,就要写 20+ 行样板代码。

更可怕的是,如果多个组件需要同样的数据,你要么:

  • 把状态提升到父组件(组件结构被绑架)
  • 用 Context 传递(样板代码更多)
  • 用 Redux/MobX(引入整个状态管理库)

但这真的是我们应该关心的吗?我们真正需要的只是那个数据而已。

范式转变:从"发请求+管理状态"到"访问数据"

TanStack Query 带来的最大思维转变是:

  1. 你不再需要"发请求",你只是在"声明数据"
  2. 你不再需要"管理状态",你只是在"消费数据"

看看这段代码:

javascript 复制代码
function UserProfile() {
  const { data: user, isLoading, error } = useQuery({
    queryKey: ['user'],
    queryFn: fetchUser
  })
  
  if (isLoading) return <div>Loading...</div>
  if (error) return <div>Error: {error.message}</div>
  
  return <div>{user?.name}</div>
}

注意到了吗?

  • ✅ 没有 useEffect,没有手动调用 fetch
  • ✅ 没有 useState,没有手动管理 loading、error、data
  • ✅ 状态自动管理,自动更新,自动同步

你只是在说:"我需要用户数据",然后直接用。

至于这个数据:

  • 从哪来(缓存?网络?)
  • 什么时候来(现在?稍后?)
  • 状态如何更新(loading → success?loading → error?)
  • 过期了要不要更新
  • 失败了要不要重试

这些都不再是你的负担,TanStack Query 会帮你处理。

不用写 useState,不用写状态更新逻辑,不用担心状态不同步。我只是需要这个数据,状态管理和我有什么关系?

数据的"存在性"而非"获取过程"和"状态管理"

传统方式让我们陷入了"过程式思维":

scss 复制代码
// 我们关心的是"过程"和"状态"
1. 定义状态:useState(loading), useState(error), useState(data)
2. 发起请求:setLoading(true)
3. 等待响应
4. 处理结果:setLoading(false), setData() 或 setError()
5. 在 JSX 中根据状态条件渲染

这就像你要喝一杯咖啡,却要自己管理咖啡机的每一个零件状态。

而 TanStack Query 让我们回归"声明式思维":

go 复制代码
// 我们关心的是"结果"
const { data, isLoading, error } = useQuery(['user'], fetchUser)
// "用户数据应该存在,请给我"
// 状态?TanStack Query 自动管理

这就像你走进咖啡店,只需要说"我要一杯咖啡",咖啡师会处理所有细节。

对比:

传统方式 TanStack Query
你是"状态管理员" 你是"数据消费者"
手动定义 loading/error/data 自动提供,直接用
手动更新每个状态 自动更新,状态同步
担心状态不一致 状态始终一致
每个组件重复写状态逻辑 写一次,到处用

实际场景:威力显现

场景一:多组件共享数据(状态同步噩梦)

传统方式的噩梦

scss 复制代码
// 组件 A
function ComponentA() {
  const [user, setUser] = useState(null)
  const [loading, setLoading] = useState(false)
  
  useEffect(() => {
    setLoading(true)
    fetchUser().then(data => {
      setUser(data)
      setLoading(false)
    })
  }, [])
  
  return <div>{user?.name}</div>
}

// 组件 B(同样的数据!)
function ComponentB() {
  const [user, setUser] = useState(null)  // 又定义一次状态
  const [loading, setLoading] = useState(false)
  
  useEffect(() => {
    setLoading(true)
    fetchUser().then(data => {  // 又发了一次请求
      setUser(data)
      setLoading(false)
    })
  }, [])
  
  return <div>{user?.email}</div>
}

// 问题:
// 1. 发了两次请求
// 2. 定义了两套状态
// 3. 如果 A 更新了数据,B 不会同步
// 4. 需要用 Context 或状态管理库来解决

// 你需要:
// - Context 传递数据(写一堆 Provider)
// - 或者状态提升(破坏组件结构)
// - 或者 Redux/MobX(引入整个状态管理体系)

为了共享一个数据,要引入整个状态管理库?这也太重了吧...

TanStack Query 的优雅

javascript 复制代码
// 组件 A
function ComponentA() {
  const { data: user } = useQuery(['user'], fetchUser)
  return <div>{user?.name}</div>
}

// 组件 B(自动共享!)
function ComponentB() {
  const { data: user } = useQuery(['user'], fetchUser)
  return <div>{user?.email}</div>
}

// 结果:
// ✅ 只发一次请求
// ✅ 不需要定义任何 useState
// ✅ 数据自动共享
// ✅ A 组件更新数据,B 组件自动同步
// ✅ 不需要 Context、Provider、Redux

同一个 queryKey,自动共享数据和状态。不用写 useState,不用担心状态不同步,就这么简单。

场景二:数据过期更新

传统方式

arduino 复制代码
// 30秒后数据可能过期了,需要重新获取吗?
// 用户切换标签页回来,需要刷新吗?
// 网络重连了,需要更新吗?

// 你需要自己写定时器、监听器、重试逻辑......

TanStack Query

kotlin 复制代码
const { data } = useQuery(['user'], fetchUser, {
  staleTime: 30000,  // 30秒内是新鲜的
  refetchOnWindowFocus: true,  // 窗口聚焦时更新
  refetchOnReconnect: true,  // 重连时更新
})

// 就这样。其他的都自动处理。

场景三:乐观更新

传统方式

scss 复制代码
// 点赞功能
const handleLike = async () => {
  // 先更新UI
  setLikes(likes + 1)
  
  try {
    await likePost(postId)
  } catch (error) {
    // 失败了要回滚
    setLikes(likes - 1)
    // 但这时 likes 可能已经被其他操作改变了...
  }
}

TanStack Query

javascript 复制代码
const mutation = useMutation(likePost, {
  onMutate: async (postId) => {
    // 乐观更新
    queryClient.setQueryData(['post', postId], old => ({
      ...old,
      likes: old.likes + 1
    }))
  },
  onError: (err, variables, context) => {
    // 自动回滚到之前的数据
    queryClient.setQueryData(['post', postId], context.previousPost)
  }
})

// 失败自动回滚,不需要手动管理

心智模型的转变

使用 TanStack Query 后,你的思考方式会发生根本变化:

以前思考

  • ❌ "我需要在这里发个请求"
  • ❌ "我要定义 loading、error、data 三个状态"
  • ❌ "请求前设置 loading=true,完成后设置 loading=false"
  • ❌ "要不要显示 loading?"
  • ❌ "数据过期了要重新请求吗?"
  • ❌ "多个组件的状态怎么同步?"

现在思考

  • ✅ "我需要这个数据"
  • ✅ "这个数据的新鲜度要求是什么?"
  • ✅ "数据变化时 UI 应该如何响应?"

你从"请求的搬运工"和"状态的管理员"变成了"数据的消费者"。

代码对比:一目了然

传统方式(100行代码)

scss 复制代码
function TodoList() {
  const [todos, setTodos] = useState([])
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState(null)
  
  // 初始加载
  useEffect(() => {
    fetchTodos()
  }, [])
  
  // 定期刷新
  useEffect(() => {
    const interval = setInterval(fetchTodos, 30000)
    return () => clearInterval(interval)
  }, [])
  
  // 窗口聚焦刷新
  useEffect(() => {
    const handleFocus = () => fetchTodos()
    window.addEventListener('focus', handleFocus)
    return () => window.removeEventListener('focus', handleFocus)
  }, [])
  
  const fetchTodos = async () => {
    setLoading(true)
    try {
      const data = await fetch('/api/todos').then(r => r.json())
      setTodos(data)
      setError(null)
    } catch (err) {
      setError(err.message)
    } finally {
      setLoading(false)
    }
  }
  
  const addTodo = async (text) => {
    try {
      const newTodo = await fetch('/api/todos', {
        method: 'POST',
        body: JSON.stringify({ text })
      }).then(r => r.json())
      
      setTodos([...todos, newTodo])
    } catch (err) {
      setError(err.message)
    }
  }
  
  if (loading) return <div>Loading...</div>
  if (error) return <div>Error: {error}</div>
  
  return (
    <div>
      {todos.map(todo => <TodoItem key={todo.id} todo={todo} />)}
    </div>
  )
}

TanStack Query 方式(20行代码)

javascript 复制代码
function TodoList() {
  const { data: todos } = useQuery(['todos'], fetchTodos, {
    staleTime: 30000,
    refetchOnWindowFocus: true,
  })
  
  const addTodoMutation = useMutation(addTodo, {
    onSuccess: () => {
      queryClient.invalidateQueries(['todos'])
    }
  })
  
  return (
    <div>
      {todos?.map(todo => <TodoItem key={todo.id} todo={todo} />)}
    </div>
  )
}

代码量减少 80%,但功能更强大、更可靠。

深层价值:架构层面的提升

使用 TanStack Query 不仅仅是代码更简洁,它还带来架构层面的好处:

1. 服务器状态与客户端状态分离

kotlin 复制代码
// 服务器状态(TanStack Query 管理,不需要你写 useState)
const { data: user } = useQuery(['user'], fetchUser)

// 客户端状态(React 管理,这才需要 useState)
const [isModalOpen, setIsModalOpen] = useState(false)

关键洞察:服务器数据根本不应该用 useState 管理!

  • 服务器数据:需要缓存、更新、同步、重试 → TanStack Query
  • 客户端 UI 状态:只在本地,组件卸载就消失 → useState

职责清晰,不再混乱。

之前用 useState 管理服务器数据,就像用螺丝刀钉钉子,工具用错了。

2. 告别状态管理样板代码

传统方式需要的状态管理代码:

scss 复制代码
// 每个数据获取都要写这些
const [data, setData] = useState(null)
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)

// 请求前
setLoading(true)
setError(null)

// 请求成功
setData(result)
setLoading(false)

// 请求失败
setError(err)
setLoading(false)

// 条件渲染
if (loading) return <Loading />
if (error) return <Error error={error} />

TanStack Query:

go 复制代码
// 一行搞定
const { data, isLoading, error } = useQuery(['key'], fetcher)

// 条件渲染(可选)
if (isLoading) return <Loading />
if (error) return <Error error={error} />

你不需要写 Redux、MobX、Zustand 来管理服务器数据,TanStack Query 就是你的服务器状态管理器。

而且,它比 Redux 更简单、更强大、更自动化。

3. 性能优化自动化

  • 请求去重
  • 并行请求优化
  • 窗口聚焦时更新
  • 后台数据预取
  • 这些都是开箱即用

4. 更好的用户体验

kotlin 复制代码
const { data, isLoading, isFetching } = useQuery(['user'], fetchUser)

// isLoading: 首次加载
// isFetching: 后台更新
// 可以显示"正在刷新"而不是整个页面重新加载

结语:思维的跃迁

TanStack Query 最大的价值不是它的 API,而是它改变了我们对数据获取和状态管理的认知。

从"我要发请求 + 我要管理状态"到"我要数据",这是一次思维的跃迁。

就像从命令式编程到声明式编程,从手动档到自动档,你不需要关心底层的每一个齿轮如何转动,你只需要说出你的目的地。

当你不再纠结于"请求"和"状态",你会发现自己有更多精力专注于真正重要的事情:构建出色的用户体验。


开始使用 TanStack Query,忘掉"发请求"和"管理状态"这两个概念吧。你只需要记住:

"我需要这个数据" --- 请求怎么发、状态怎么管,交给 TanStack Query。

bash 复制代码
npm install @tanstack/react-query

你的前端开发体验,即将焕然一新。

相关推荐
琦遇1 小时前
Vue3使用vuedraggable实现拖拽排序
前端
银月流苏1 小时前
Vue 深度选择器 `:deep` 使用说明
前端
程序媛ing1 小时前
React + ECharts 动态折线图实现
前端·react.js
广州华水科技2 小时前
单北斗GNSS变形监测在地质灾害与基础设施安全中的应用与优势分析
前端
程序员鱼皮2 小时前
又被 Cursor 烧了 1 万块,我麻了。。。
前端·后端·ai·程序员·大模型·编程
孟祥_成都2 小时前
nextjs 16 基础完全指南!(一) - 初步安装
前端·next.js
程序员爱钓鱼2 小时前
使用简单 JSON + 自定义 t 函数实现轻量多语言国际化(无需 next-intl)
前端·javascript·trae
一 乐2 小时前
助农平台|基于SprinBoot+vue的助农服务系统(源码+数据库+文档)
前端·javascript·数据库·vue.js·ecmascript·springboot
vivo互联网技术2 小时前
浅谈 AI 搜索前端打字机效果的实现方案演进
前端·vue·dom