news 2026/5/22 1:14:11

React 从入门到生产(四):自定义 Hook

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
React 从入门到生产(四):自定义 Hook

创作者:Yardon |GitHub:github.com/YardonYan |版本:v1.0


为什么需要自定义 Hook

假设你在三个不同的页面都要做一个功能:用户输入搜索词后,等 300ms 没动静了才发请求(防抖)。

你可以在每个页面都写一遍:

function SearchPage1() { const [query, setQuery] = useState(''); const [results, setResults] = useState([]); useEffect(() => { const timeout = setTimeout(() => { if (query) fetchResults(query).then(setResults); }, 300); return () => clearTimeout(timeout); }, [query]); // ... 这段代码要复制三份! }

这就是典型的代码重复。防抖逻辑本身是独立的,完全可以抽出来。而且当产品说"改成 500ms"时,你只要改一个地方。

自定义 Hook 就是为这个场景设计的——把可复用的逻辑封装成函数,这个函数内部可以用其他 Hook


自定义 Hook 的基本结构

自定义 Hook 本质上就是一个普通的 JavaScript 函数,函数名以use开头,内部可以调用其他 Hook。

functionuseDebounce(value,delay=300){const[debouncedValue,setDebouncedValue]=useState(value);useEffect(()=>{consttimeout=setTimeout(()=>{setDebouncedValue(value);},delay);return()=>clearTimeout(timeout);},[value,delay]);returndebouncedValue;}

这就是一个 Hook。它用到了useStateuseEffect,所以它自己也是个 Hook(React 的规则:只有 Hook 才能调用其他 Hook)。

用法

function SearchPage() { const [query, setQuery] = useState(''); const debouncedQuery = useDebounce(query, 300); useEffect(() => { if (debouncedQuery) fetchResults(debouncedQuery); }, [debouncedQuery]); return <input value={query} onChange={(e) => setQuery(e.target.value)} />; }

现在防抖逻辑只在一处定义了,三个页面都可以用。


经典案例:useDebounce

继续深化这个案例,加上更多防抖的变体:

functionuseDebouncedValue(value,delay=300){const[debouncedValue,setDebouncedValue]=useState(value);useEffect(()=>{consttimer=setTimeout(()=>{setDebouncedValue(value);},delay);return()=>clearTimeout(timer);},[value,delay]);returndebouncedValue;}// 防抖版表单:用户停止输入后才更新functionuseDebouncedForm(initialValues,delay=300){const[values,setValues]=useState(initialValues);// 为每个字段单独防抖constdebouncedValues={};for(constkeyinvalues){debouncedValues[key]=useDebouncedValue(values[key],delay);}functionhandleChange(key,value){setValues((prev)=>({...prev,[key]:value}));}return{values,debouncedValues,handleChange};}

经典案例:useFetch

数据获取是另一个重复高频的��景:

functionuseFetch(url,options={}){const[data,setData]=useState(null);const[loading,setLoading]=useState(true);const[error,setError]=useState(null);useEffect(()=>{constcontroller=newAbortController();asyncfunctionfetchData(){try{setLoading(true);setError(null);constres=awaitfetch(url,{...options,signal:controller.signal});if(!res.ok)thrownewError(`HTTP${res.status}`);constjson=awaitres.json();setData(json);}catch(err){if(err.name!=='AbortError'){setError(err.message);}}finally{setLoading(false);}}fetchData();return()=>controller.abort();},[url,JSON.stringify(options)]);return{data,loading,error,refetch:()=>/* ... */};}

用法变得极其简单

function UserList() { const { data, loading, error } = useFetch('/api/users'); if (loading) return <Spinner />; if (error) return <Error msg={error} />; return <ul>{data.map(u => <li key={u.id}>{u.name}</li>)}</ul>; }

一行代码替代了 30 行重复的请求/加载/错误逻辑。这就是 Hook 的价值。


经典案例:useLocalStorage

把数据存进浏览器本地存储,同时保持和 React 状态的同步:

functionuseLocalStorage(key,initialValue){const[storedValue,setStoredValue]=useState(()=>{try{constitem=window.localStorage.getItem(key);returnitem?JSON.parse(item):initialValue;}catch(error){console.warn(`读取 localStorage key "${key}" 失败:`,error);returninitialValue;}});constsetValue=useCallback((value)=>{try{constvalueToStore=valueinstanceofFunction?value(storedValue):value;setStoredValue(valueToStore);window.localStorage.setItem(key,JSON.stringify(valueToStore));}catch(error){console.warn(`写入 localStorage key "${key}" 失败:`,error);}},[key,storedValue]);return[storedValue,setValue];}

用法:记住用户的偏好

function App() { const [theme, setTheme] = useLocalStorage('theme', 'dark'); const [language, setLanguage] = useLocalStorage('language', 'zh-CN'); // 用户刷新页面后,主题和语言自动恢复 return <ThemeProvider theme={theme}>...</ThemeProvider>; }

Hook 组合:更复杂的逻辑

你可以把多个自定义 Hook 组合在一起,形成更强大的逻辑:

// 一个组合 Hook:用户搜索 + 防抖 + 缓存functionuseSearch(query,options={}){const{baseUrl='/api/search',delay=300,cache=true}=options;constdebouncedQuery=useDebounce(query,delay);constcacheKey=`${baseUrl}:${debouncedQuery}`;const[cachedData,setCachedData]=useLocalStorage(`search-cache`,{});const[freshData,setFreshData]=useState(null);// 优先用缓存constdata=cache&&cachedData[cacheKey]?cachedData[cacheKey]:freshData;useEffect(()=>{if(!debouncedQuery)return;// 检查缓存constcached=cachedData[cacheKey];if(cached&&Date.now()-cached.timestamp<5*60*1000){// 5 分钟内的缓存直接用return;}// 发新请求fetch(`${baseUrl}?q=${encodeURIComponent(debouncedQuery)}`).then((r)=>r.json()).then((d)=>{setFreshData(d);// 更新缓存setCachedData((prev)=>({...prev,[cacheKey]:{...d,timestamp:Date.now()}}));});},[debouncedQuery,baseUrl]);return{data,isLoading:!debouncedQuery||!data};}

这就是所谓的「管道式」架构——每个 Hook 只做一件事,组合起来就拥有了完整功能。


测试自定义 Hook

自定义 Hook 的测试需要一点特殊处理——React Testing Library 专门为 Hook 提供了renderHook

import{renderHook,act}from'@testing-library/react';test('useDebounce 应该延迟返回新值',()=>{const{result,rerender}=renderHook(({value,delay})=>useDebounce(value,delay),{initialProps:{value:'hello',delay:300}});expect(result.current).toBe('hello');// 修改值rerender({value:'world',delay:300});expect(result.current).toBe('hello');// 还没到 300ms// 等 300msjest.advanceTimersByTime(300);expect(result.current).toBe('world');});

本章小结

概念一句话总结
自定义 Hookuse开头的函数,内部可调用其他 Hook
useDebounce延迟更新值,常用于搜索输入
useFetch封装请求逻辑,一行代码搞定数据获取
useLocalStorage持久化状态到浏览器本地存储
Hook 组合把多个简单 Hook 组合成复杂逻辑

自定义 Hook 把可复用的逻辑抽离出来——这是 React 应用架构的核心技能。下一章我们聊状态管理——当组件树越来越深时,怎么让状态在任意位置都能访问。


📌创作者:Yardon | 🏠个人网站:GlimmerAI.top

📖 本章是「React 从入门到生产」系列的第 4 章。上一章:副作用与数据获取 | 下一章:状态管理选型

🌟 如果你觉得有帮助,欢迎访问 GlimmerAI.top 查看我的更多作品。欢迎大家来观看!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/22 1:11:24

论文AI率超标不用愁:4种实用方法+3个提速技巧

好不容易写完的论文,一测AI率居然高达50%,可学校要求必须低于10%,是不是瞬间觉得之前熬的夜都白费了?改了好几个来回AI率没降多少,反而把内容改得不通顺?别慌,今天就把我亲测有效的降AI率全方案…

作者头像 李华
网站建设 2026/5/22 0:54:51

5大优势解锁跨平台直播聚合:PureLive如何重塑你的直播观看体验

5大优势解锁跨平台直播聚合:PureLive如何重塑你的直播观看体验 【免费下载链接】pure_live A Flutter project can make you watch live with ease. 项目地址: https://gitcode.com/gh_mirrors/pu/pure_live 你是否厌倦了在不同直播平台间来回切换&#xff1…

作者头像 李华
网站建设 2026/5/22 0:50:41

1746-HSCE逻辑控制器

Allen-Bradley 1746-HSCE 是一款专为 SLC 500 系列设计的高速计数器模块,用于精确处理高速脉冲信号和编码器反馈。产品特点(15条):1746-HSCE 支持 4 个独立的高速计数器通道,可同时处理多路信号最大输入频率达 100 kHz…

作者头像 李华
网站建设 2026/5/22 0:50:39

环保型混凝土施工的环境影响计量与施工方案决策方法【附方案】

✨ 长期致力于施工决策、环保型混凝土、环境影响计量、材料选型、养护措施选择研究工作,擅长数据搜集与处理、建模仿真、程序编写、仿真设计。 ✅ 专业定制毕设、代码 ✅ 如需沟通交流,点击《获取方式》 (1)离散事件模拟与生命周期…

作者头像 李华
网站建设 2026/5/22 0:49:21

Rust分布式系统最佳实践:构建高可用、高性能的后端服务

Rust分布式系统最佳实践:构建高可用、高性能的后端服务 引言 在当今云原生时代,分布式系统已经成为后端开发的标配。作为一名从Python转向Rust的后端开发者,我深刻体会到Rust在构建分布式系统方面的独特优势。Rust的内存安全、零成本抽象和出…

作者头像 李华