React面试宝典【Part7:常用自定义hook和其他案例】
2026-08-03
1. 手写 useDebounce 防抖 Hook
场景:搜索框输入、窗口 resize、滚动、高频点击。
好处
逻辑复用:一次封装,到处使用
自动清理副作用:组件卸载 / 依赖变化时自动清定时器
组件代码极干净,只关心业务,不关心定时器细节
import { useEffect, useState } from 'react';
function useDebounce(value, delay = 300) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}
用法
const [value, setValue] = useState('');
const debouncedValue = useDebounce(value, 500);
2. 手写 useThrottle 节流 Hook
import { useEffect, useRef, useState } from 'react';
function useThrottle(value, limit = 300) {
const [throttledValue, setThrottledValue] = useState(value);
const lastRan = useRef(Date.now());
useEffect(() => {
const handler = setTimeout(() => {
if (Date.now() - lastRan.current >= limit) {
setThrottledValue(value);
lastRan.current = Date.now();
}
}, limit - (Date.now() - lastRan.current));
return () => clearTimeout(handler);
}, [value, limit]);
return throttledValue;
}
3. 手写 useFetch(loading、error、data)
场景:几乎所有请求接口的组件。
useFetch 的价值
- 统一请求逻辑:loading、error、data 全部封装
- 避免重复代码
- 统一错误处理、统一加载样式
- 组件只拿结果,不关心请求过程
import { useState, useEffect } from 'react';
function useFetch(url, options = {}) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
setError(null);
try {
const res = await fetch(url, options);
if (!res.ok) throw new Error('请求失败');
const result = await res.json();
setData(result);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
fetchData();
}, [url, options]);
return { data, loading, error };
}
4. 简易 useReducer 手写
场景:状态逻辑复杂、一个事件要改多个状态。
useReducer 的价值
- 状态更新可预测,集中管理
- 复杂逻辑收敛到 reducer,组件更干净
- 方便日志、回放、测试
import { useState } from 'react';
function useReducer(reducer, initialState) {
const [state, setState] = useState(initialState);
function dispatch(action) {
const nextState = reducer(state, action);
setState(nextState);
}
return [state, dispatch];
}
使用示例
function reducer(state, action) {
switch (action.type) {
case 'inc': return { count: state.count + 1 };
default: return state;
}
}
const [state, dispatch] = useReducer(reducer, { count: 0 });
5. 实现受控表单组件
场景:登录、表单页、大批量输入框。
封装后价值
- 一个 onChange 管理所有表单项
- 统一做校验、提交、重置
- 代码极度简洁、可复用
import { useState } from 'react';
function FormControl() {
const [form, setForm] = useState({
username: '',
password: ''
});
const handleChange = (e) => {
const { name, value } = e.target;
setForm(prev => ({ ...prev, [name]: value }));
};
const handleSubmit = (e) => {
e.preventDefault();
console.log(form);
};
return (
<form onSubmit={handleSubmit}>
<input
name="username"
value={form.username}
onChange={handleChange}
placeholder="用户名"
/>
<input
name="password"
type="password"
value={form.password}
onChange={handleChange}
placeholder="密码"
/>
<button type="submit">提交</button>
</form>
);
}
6. 简易虚拟列表思路(面试口述版)
场景:长列表(1000+ 条数据)
价值
- 只渲染可视区域 DOM,性能提升几十倍
- 滚动流畅,不阻塞主线程
- 逻辑复用,所有长列表都能用
核心思想:只渲染可视区域内的项,滚动时动态替换内容,减少 DOM 数量。
步骤
- 固定容器高度,设置
overflow-y: auto - 知道每项高度
itemHeight - 监听
scrollTop - 计算开始索引:
startIdx = Math.floor(scrollTop / itemHeight) - 计算结束索引:
endIdx = startIdx + visibleCount - 截取数据
list.slice(startIdx, endIdx)渲染 - 用
paddingTop撑起滚动条高度,模拟长列表
function VirtualList({ list, itemHeight = 50, visibleCount = 10 }) {
const [scrollTop, setScrollTop] = useState(0);
const containerHeight = itemHeight * visibleCount;
const startIdx = Math.floor(scrollTop / itemHeight);
const endIdx = startIdx + visibleCount;
const showList = list.slice(startIdx, endIdx);
return (
<div
style={{ height: containerHeight, overflowY: 'auto' }}
onScroll={e => setScrollTop(e.target.scrollTop)}
>
<div style={{ paddingTop: startIdx * itemHeight }}>
{showList.map((it, i) => (
<div key={i} style={{ height: itemHeight }}>{it}</div>
))}
</div>
</div>
);
}
7. HOC 高阶组件
场景:跨组件复用逻辑(权限、埋点、加载、样式)
好处
高阶复用:不修改组件,直接增强能力
权限、日志、样式、加载都能统一封装
经典 React 逻辑复用模式(Hook 出现前的主流)
// 定义 HOC
function withLoading(WrappedComponent) {
return function WithLoadingComponent({ loading, ...props }) {
if (loading) return <div>加载中...</div>;
return <WrappedComponent {...props} />;
};
}
// 使用
function List({ data }) {
return <div>{data}</div>;
}
const ListWithLoading = withLoading(List);
8. Render Props
function MouseRender({ children }) {
const [pos, setPos] = useState({ x: 0, y: 0 });
return (
<div onMouseMove={e => setPos({ x: e.clientX, y: e.clientY })}>
{children(pos)}
</div>
);
}
// 使用
function App() {
return (
<MouseRender>
{({ x, y }) => <div>x:{x}, y:{y}</div>}
</MouseRender>
);
}