声明你要的数据
使用 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>
}
这里有两个根深蒂固的思维模式:
- 我们在"发请求"
- 我们在"管理状态" - loading、error、data 三个状态要手动定义、手动更新、手动同步
每个请求都需要:
- 定义 3 个 useState
- 在请求前后更新这些状态
- 处理各种边界情况(取消请求、组件卸载、竞态条件...)
- 在 JSX 里写一堆条件渲染
一个简单的数据获取,就要写 20+ 行样板代码。
更可怕的是,如果多个组件需要同样的数据,你要么:
- 把状态提升到父组件(组件结构被绑架)
- 用 Context 传递(样板代码更多)
- 用 Redux/MobX(引入整个状态管理库)
但这真的是我们应该关心的吗?我们真正需要的只是那个数据而已。
范式转变:从"发请求+管理状态"到"访问数据"
TanStack Query 带来的最大思维转变是:
- 你不再需要"发请求",你只是在"声明数据"
- 你不再需要"管理状态",你只是在"消费数据"
看看这段代码:
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
你的前端开发体验,即将焕然一新。