React Hooks:从基础到实践的全面指南
React Hooks 自 2018 年发布以来彻底改变了我们编写 React 组件的方式。它们允许我们在不编写 class 的情况下使用 state 以及其他的 React 特性让代码更加简洁、可复用且易于理解。本文将深入探讨几个最核心和常用的 Hooks包括 React 内置的useState、useEffect、useMemo以及强大的数据获取和缓存库 TanStack Query (tanstack/react-query) 提供的useQuery、useMutation、useQueryClient还有用于路由导航的useNavigate。通过理论与实例相结合帮助您全面掌握它们的使用场景和最佳实践。一、React 内置 Hooks 核心详解1.1 useState管理组件的本地状态useState是构建交互式 UI 的基石。它让你在函数组件中拥有并更新本地状态。核心概念useState(initialValue)接收一个初始状态值。返回一个数组[state, setState]。state当前的状态值。setState一个用于更新状态的函数。示例import{useState}fromreact;functionCounter(){const[count,setCount]useState(0);// 初始值为 0constincrement(){// 状态更新函数可以接收一个函数以确保基于最新的状态值进行更新setCount(prevCountprevCount1);};return(divpCount:{count}/pbutton onClick{increment}/button/div);}1.2 useEffect处理副作用与生命周期useEffect让你可以执行那些可能产生“副作用”的操作例如数据获取、订阅或手动更改 DOM。它巧妙地整合了组件挂载、更新和卸载的逻辑。核心概念useEffect(didUpdate, dependencies?)didUpdate包含副作用逻辑的函数。这个函数可以选择性地返回一个清理函数。dependencies?依赖数组。useEffect的行为取决于这个数组。[](空数组)副作用仅在组件挂载时执行一次相当于componentDidMount。[dep1, dep2]副作用在组件挂载以及dep1或dep2发生变化时执行。undefined(无)副作用在每次渲染后都会执行相当于componentDidMount和componentDidUpdate的组合。示例数据获取import{useState,useEffect}fromreact;functionUserProfile({userId}){const[user,setUser]useState(null);const[loading,setLoading]useState(true);useEffect((){letisCancelledfalse;// 用于防止在组件卸载后设置状态asyncfunctionfetchUserData(){setLoading(true);try{constresponseawaitfetch(/api/users/${userId});constuserDataawaitresponse.json();if(!isCancelled){setUser(userData);}}catch(error){if(!isCancelled){console.error(Fetch error:,error);}}finally{if(!isCancelled){setLoading(false);}}}if(userId){fetchUserData();}// 清理函数在组件卸载或下次 effect 执行前运行return(){isCancelledtrue;};},[userId]);// 依赖 userId当 userId 变化时重新执行if(loading)returnpLoading.../p;if(!user)returnpNo user found./p;returnh1{user.name}/h1;}1.3 useMemo优化昂贵的计算useMemo是性能优化的利器。它可以“记住”一个计算的结果只有当它的依赖项发生改变时才会重新计算。核心概念useMemo(calculateValue, dependencies)calculateValue一个返回所需值的函数。dependencies一个依赖项数组。calculateValue只有在任何一个依赖项改变时才会被重新执行。示例缓存复杂计算结果import{useState,useMemo}fromreact;functionExpensiveList({items,filterTerm}){const[count,setCount]useState(0);// 只有 items 或 filterTerm 改变时才会重新过滤列表constfilteredItemsuseMemo((){console.log(Re-running expensive calculation...);// 用于演示returnitems.filter(itemitem.name.includes(filterTerm));},[items,filterTerm]);return(divpCount:{count}button onClick{()setCount(cc1)}/button/pul{filteredItems.map(itemli key{item.id}{item.name}/li)}/ul/div);}二、TanStack Query服务器状态管理的专家在现代 Web 应用中与服务器的数据交互非常频繁。手动管理加载、错误、缓存等逻辑既繁琐又容易出错。TanStack Query (原 React Query) 为此而生它提供了强大的工具来简化服务器状态的管理。2.1 useQuery优雅地获取数据useQuery是获取和管理服务器数据的首选 Hook。它内置了缓存、后台同步、请求去重、错误处理等强大功能。核心概念useQuery({ queryKey, queryFn, ...options })queryKey一个唯一标识查询的数组例如[user, userId]。它是缓存和失效的关键。queryFn一个返回 Promise 的异步函数用于执行实际的 API 请求。options其他配置选项如enabled(是否启用查询)、staleTime(数据被认为是陈旧的时间)、cacheTime(数据在缓存中保留的时间) 等。示例import{useQuery}fromtanstack/react-query;functionUserDetail({userId}){const{data:user,isLoading,isError,error,refetch,// 提供一个手动刷新数据的函数}useQuery({queryKey:[user,userId],// 查询键queryFn:async(){constresponseawaitfetch(/api/users/${userId});if(!response.ok){thrownewError(Network response was not ok);}returnresponse.json();},enabled:!!userId,// 仅当 userId 存在时才发起请求});if(isLoading)returndivLoading.../div;if(isError)returndivError:{error.message}/div;return(divh2{user.name}/h2p{user.email}/pbutton onClick{()refetch()}Refetch/button/div);}2.2 useMutation处理数据变更useMutation用于处理创建、更新、删除等修改服务器数据的操作。它专注于处理这些“写”操作的生命周期。核心概念useMutation({ mutationFn, onSuccess, onError, ...options })mutationFn一个执行写操作的异步函数。onSuccess(data, variables, context)操作成功时的回调。onError(error, variables, context)操作失败时的回调。mutate(variables)触发 mutation 的函数需要传入mutationFn所需的参数。示例import{useMutation,useQueryClient}fromtanstack/react-query;functionCreateTodoForm(){constqueryClientuseQueryClient();constmutationuseMutation({mutationFn:async(newTodo){constresponseawaitfetch(/api/todos,{method:POST,headers:{Content-Type:application/json},body:JSON.stringify(newTodo),});if(!response.ok){thrownewError(Failed to create todo);}returnresponse.json();},onSuccess:(newTodo){// 成功后可以手动更新缓存或使其失效// 例如使所有 todos 的查询失效让它们在下次渲染时重新获取queryClient.invalidateQueries({queryKey:[todos]});// 或者更精确地更新特定查询的缓存// queryClient.setQueryData([todos], (oldTodos) [...oldTodos, newTodo]);},onError:(error){console.error(Create error:,error);}});consthandleSubmit(e){e.preventDefault();constforme.target;consttitleform.title.value.trim();if(title){mutation.mutate({title,completed:false});form.reset();// 重置表单}};return(form onSubmit{handleSubmit}input nametitleplaceholderWhat needs to be done?/button typesubmitdisabled{mutation.isPending}{mutation.isPending?Saving...:Add Todo}/button{mutation.isErrorspan style{{color:red}}Error:{mutation.error.message}/span}/form);}2.3 useQueryClient访问查询客户端useQueryClientHook 允许你在组件内部访问 TanStack Query 的QueryClient实例。这让你可以手动控制缓存比如预取数据、更新缓存或使其失效。核心概念const queryClient useQueryClient();通过queryClient对象你可以调用其方法如queryClient.invalidateQueries()、queryClient.setQueryData()、queryClient.prefetchQuery()等。在useMutation中的应用上面useMutation的示例已经展示了useQueryClient的一个重要用法在数据变更成功后使相关的缓存失效以确保 UI 显示最新的数据。三、Hooks 之间的关系与区别为什么我们需要这么多许多开发者可能会疑惑既然useQuery和useMutation已经能很好地处理数据的 CRUD (创建、读取、更新、删除)并且useQuery本身就带有缓存功能那为什么还需要useEffect和useMemo呢它们之间有何不同3.1 useEffect vs. useQuery/useMutation职责划分useQuery和useMutation的核心职责是管理服务器状态 (Server State)。它们专注于与后端 API 的交互包括获取、修改、缓存和同步数据。useEffect的职责则更广泛它处理所有副作用 (Side Effects)。副作用是指那些不在 React 渲染过程中发生但会影响组件或外部世界的行为。useQuery和useMutation本身也是基于useEffect构建的但它们将其封装成了针对特定场景的专用工具。useEffect的典型应用场景包括订阅外部事件例如监听浏览器窗口大小变化 (window.addEventListener(resize, handler))、键盘事件、或者 WebSocket 消息。手动操作 DOM在组件挂载后获取 DOM 元素并进行聚焦 (focus())、滚动 (scrollIntoView)、或执行自定义动画。启动和清理计时器使用setInterval或setTimeout并在组件卸载时通过清理函数清除它们防止内存泄漏。初始化第三方库在组件挂载时初始化像 Chart.js 图表、Mapbox 地图等需要 DOM 元素才能工作的库。执行非数据获取类的副作用例如在某个状态变化后发送 Google Analytics 事件。总结useQuery/useMutation专门处理服务器数据而useEffect处理所有其他类型的副作用。3.2 useMemo vs. useQuery 缓存缓存的不同层面useQuery的缓存和useMemo的缓存虽然都叫“缓存”但它们解决的问题和作用的层面完全不同。useQuery的缓存目的缓存服务器返回的数据 (Server Data)。好处减少不必要的网络请求提高应用响应速度避免重复加载相同的数据。范围通常存储在 TanStack Query 的全局缓存实例中可以在多个组件间共享。触发由queryKey和网络请求决定。useMemo的缓存目的缓存组件渲染期间的计算结果 (Computed Values)。好处避免在每次组件重新渲染时都执行昂贵的计算提升渲染性能。范围仅存在于组件的内存中与组件实例绑定。触发由useMemo的依赖数组 (depsarray) 决定。举例说明假设你用useQuery获取了一个包含大量用户的数组users。现在你想根据用户输入的searchTerm来过滤这个列表。// 不使用 useMemofunctionUserList({searchTerm}){const{data:users[],isLoading}useQuery({queryKey:[users],queryFn:fetchUsers});// 每次 searchTerm 改变导致组件重渲染时这个过滤操作都会被执行// 如果 users 很大这会很耗性能。constfilteredUsersusers.filter(useruser.name.toLowerCase().includes(searchTerm.toLowerCase()));if(isLoading)returndivLoading.../div;returnul{filteredUsers.map(userli key{user.id}{user.name}/li)}/ul;}// 使用 useMemofunctionUserList({searchTerm}){const{data:users[],isLoading}useQuery({queryKey:[users],queryFn:fetchUsers});// 只有当 users 数组 或 searchTerm 改变时才会重新执行过滤操作constfilteredUsersuseMemo((){console.log(Filtering users...);// 仅在必要时打印证明计算被跳过returnusers.filter(useruser.name.toLowerCase().includes(searchTerm.toLowerCase()));},[users,searchTerm]);// 注意依赖项if(isLoading)returndivLoading.../div;returnul{filteredUsers.map(userli key{user.id}{user.name}/li)}/ul;}在这个例子中useQuery缓存了从服务器获取的原始users数据。useMemo缓存了对users数据进行过滤计算后的结果filteredUsers。useQuery保证了users不会被重复请求而useMemo保证了users没变时过滤计算不会重复执行。两者配合使用能同时优化网络请求和渲染性能。总结useQuery缓存服务器数据useMemo缓存组件内的计算结果。四、React Router DOM编程式导航当应用变得复杂路由逻辑不再仅仅依赖于用户点击链接时我们就需要编程式导航。4.1 useNavigate掌控导航方向useNavigateHook 提供了一个navigate函数让你可以在代码的任何地方执行导航操作。核心概念const navigate useNavigate();navigate(to, options?)执行导航。to目标路径字符串或偏移量数字如-1表示后退。options?可选配置如{ replace: boolean }(替换历史记录栈顶)、{ state: any }(传递状态)。示例登录后的重定向import{useState}fromreact;import{useNavigate}fromreact-router-dom;functionLoginPage(){const[username,setUsername]useState();const[password,setPassword]useState();const[error,setError]useState();constnavigateuseNavigate();// 获取 navigate 函数consthandleSubmitasync(e){e.preventDefault();setError();try{constresponseawaitfetch(/api/login,{method:POST,headers:{Content-Type:application/json},body:JSON.stringify({username,password}),});if(response.ok){// 登录成功导航到仪表盘navigate(/dashboard,{replace:true});// 使用 replace 防止用户点后退按钮回到登录页}else{consterrorDataawaitresponse.json();setError(errorData.message||Login failed);}}catch(err){setError(Network error);}};return(form onSubmit{handleSubmit}input value{username}onChange{(e)setUsername(e.target.value)}placeholderUsername/input typepasswordvalue{password}onChange{(e)setPassword(e.target.value)}placeholderPassword/button typesubmitLog In/button{errorp style{{color:red}}{error}/p}/form);}结语本文系统地介绍了useState,useEffect,useMemo这三个 React 核心 Hooks以及useQuery,useMutation,useQueryClient这套强大的 TanStack Query 工具集还有useNavigate这个路由导航利器。我们还深入探讨了useEffect和useMemo与useQuery/useMutation的区别明确了它们各自独特的职责和应用场景。理解并熟练运用这些 Hooks是构建现代化、高性能 React 应用的关键。希望这篇指南能为您提供清晰、实用的参考。