
如果你刚接触React Hooks你可能会觉得学了很多useXxx() API但还是写不出好代码。原因很简单网上大多数教程都在教你怎么调用一个Hook而不是教你在真实场景中应该用哪个、为什么用它。本文直接用真实场景说话。一、useState vs useReducer不是复杂度的区别你可能看过这种说法“简单状态用useState复杂状态用useReducer”。这个建议害了很多人。实际上判断标准不是复杂度而是逻辑关联性。什么时候用useStateconst[count,setCount]useState(0)const[name,setName]useState()const[isOpen,setIsOpen]useState(false)这些状态之间互不依赖每个独立变化。用useState足够。什么时候用useReducer// 表单状态多个字段需要同时更新const[form,dispatch]useReducer(formReducer,{name:,email:,phone:,address:,errors:{}})functionformReducer(state,action){switch(action.type){caseSET_FIELD:return{...state,[action.field]:action.value}caseSET_ERRORS:return{...state,errors:action.errors}caseRESET:returninitialStatedefault:returnstate}}判断标准很简单如果更新一个状态时需要同时知道其他状态的值就用useReducer。二、useEffect80%的人用错了依赖数组useEffect是React Hooks里最容易出错的地方。规则1依赖数组应该包含所有你用到的东西// ❌ 错误用了count但没在依赖里声明useEffect((){document.title点击了${count}次},[])// ✅ 正确useEffect((){document.title点击了${count}次},[count])规则2不要在useEffect里更新它依赖的值除非有条件// ❌ 无限循环useEffect((){setCount(count1)},[count])// ✅ 有条件useEffect((){if(count10){setCount(count1)}},[count])规则3数据请求应该在useEffect里做但要避免竞态useEffect((){letcancelledfalsefetch(/api/user/${userId}).then(resres.json()).then(data{if(!cancelled){setUser(data)}})return(){cancelledtrue// 组件卸载或userId变化时取消旧请求}},[userId])三、自定义Hook把逻辑从组件中抽出来这是Hooks最大的价值所在也是大多数人没有充分利用的功能。不好的写法逻辑和UI混在一起functionUserList(){const[users,setUsers]useState([])const[loading,setLoading]useState(true)const[error,setError]useState(null)useEffect((){fetch(/api/users).then(resres.json()).then(data{setUsers(data)setLoading(false)}).catch(err{setError(err.message)setLoading(false)})},[])if(loading)returndiv加载中.../divif(error)returndiv错误{error}/divreturn(ul{users.map(uli key{u.id}{u.name}/li)}/ul)}好的写法逻辑抽成自定义HookfunctionuseUsers(){const[users,setUsers]useState([])const[loading,setLoading]useState(true)const[error,setError]useState(null)useEffect((){fetch(/api/users).then(resres.json()).then(data{setUsers(data)setLoading(false)}).catch(err{setError(err.message)setLoading(false)})},[])return{users,loading,error}}functionUserList(){const{users,loading,error}useUsers()if(loading)returndiv加载中.../divif(error)returndiv错误{error}/divreturn(ul{users.map(uli key{u.id}{u.name}/li)}/ul)}抽出来之后useUsers可以在任何组件中复用测试也更方便。常用自定义Hook示例// 监听窗口大小functionuseWindowSize(){const[size,setSize]useState({width:window.innerWidth,height:window.innerHeight})useEffect((){consthandle()setSize({width:window.innerWidth,height:window.innerHeight})window.addEventListener(resize,handle)return()window.removeEventListener(resize,handle)},[])returnsize}// 本地存储functionuseLocalStorage(key,initialValue){const[value,setValue]useState((){conststoredlocalStorage.getItem(key)returnstored?JSON.parse(stored):initialValue})useEffect((){localStorage.setItem(key,JSON.stringify(value))},[key,value])return[value,setValue]}四、useMemo和useCallback不要过早优化大部分人用这两个Hook的时机是错的。什么时候真的需要用// ❌ 不需要计算很简单constdoubleduseMemo(()count*2,[count])// ✅ 需要计算代价很大constsortedUsersuseMemo((){returnusers.sort((a,b)expensiveCompare(a,b))},[users])黄金法则先不用useMemo和useCallback等真的出现性能问题在React DevTools Profiler里能测出来再添加。五、常见模式和反模式不要写复杂的useEffect// ❌ 反模式一个useEffect做太多事useEffect((){fetchUser()fetchPosts()setupWebSocket()trackPageView()},[userId])// ✅ 拆成多个useEffect((){fetchUser()},[userId])useEffect((){fetchPosts()},[userId])useEffect((){setupWebSocket();returncleanup},[userId])useEffect((){trackPageView()},[])每个useEffect只做一件事。不要滥用状态// ❌ 可以从已有数据计算出来的就不要存const[firstName,setFirstName]useState()const[lastName,setLastName]useState()const[fullName,setFullName]useState()// ✅ 直接用constfullNamefirstName lastName六、迁移建议从Class到Hooks如果你的项目还在用Class组件这是一个安全的迁移路径新组件全部用Hooks旧组件不急着改遇到bug或新需求时再改优先把逻辑密集型的Class组件改成Hooks因为自定义Hook可以显著降低复杂度结语Hooks不仅仅是另一种写法它是一种更自然的把UI和逻辑分离开的思路。掌握Hooks的关键不是背API而是学会在工作中识别“这段逻辑能抽出来用自定义Hook吗”多写、多拆、多复用自然就熟练了。