⭐ Level 1: 必考题(15题)
📌 Hooks 机制(5题)
2. useEffect 的依赖数组应该怎么写?什么时候会导致无限循环?
🔍 追问:
- "为什么这段代码会无限循环?"
useEffect(() => {
setData({ value: 1 }); // 每次创建新对象
}, [data]);
- "如何处理对象/数组依赖?"
- "useEffect 的依赖对比用的是什么?"
- "eslint-plugin-react-hooks 的作用?"
3. useEffect 的清理函数什么时候执行?如何用它避免内存泄漏?
🔍 追问:
- "清理函数在组件卸载前还是卸载后执行?"
- "如何取消未完成的异步请求?"
- "如何处理 setInterval 的清理?"
- "WebSocket 连接如何清理?"
💻 可能的现场写:
useEffect(() => {
const controller = new AbortController();
fetch(url, { signal: controller.signal })
.then(setData);
return () => controller.abort();
}, [url]);
4. useCallback 和 useMemo 什么时候用?滥用会有什么问题?
🔍 追问:
- "这两个 Hook 的区别是什么?"
- "什么情况下用它们反而降低性能?"
- "如何判断是否需要优化?"
- "子组件用了 React.memo,父组件一定要用 useCallback 吗?"
5. 如何自定义一个 Hook?设计自定义 Hook 要注意什么?
🔍 追问:
- "现场实现一个 useDebounce Hook"
- "现场实现一个 useLocalStorage Hook"
- "如何处理 Hook 内部的异步逻辑?"
- "返回数组还是对象?为什么?"
💻 可能的现场写:
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}
7. useRef 除了获取 DOM,还有什么用途?
🔍 追问:
- "如何用 useRef 保存上一次的值?"
- "useRef 和 useState 的区别?"
- "为什么 useRef 的改变不会触发重新渲染?"
- "如何在 useEffect 中访问最新的 state?"
💻 可能的现场写:
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
📌 性能优化(4题)
3. 如何避免组件不必要的 re-render?
🔍 追问:
- "列举 5 种避免重渲染的方法"
- "Context 导致的重渲染如何优化?"
- "为什么子组件会跟着父组件重渲染?"
- "props 没变,组件为什么还重渲染?"
4. 大列表渲染如何优化?虚拟滚动的原理是什么?
🔍 追问:
- "虚拟滚动的核心思想是什么?"
- "react-window 和 react-virtualized 的区别?"
- "如何计算可视区域的元素?"
- "动态高度的列表如何处理?"
💻 可能的现场写:
// 简单的虚拟滚动实现思路
function VirtualList({ items, itemHeight, containerHeight }) {
const [scrollTop, setScrollTop] = useState(0);
const startIndex = Math.floor(scrollTop / itemHeight);
const endIndex = Math.ceil((scrollTop + containerHeight) / itemHeight);
const visibleItems = items.slice(startIndex, endIndex);
// 实现滚动和渲染逻辑
}
6. 什么情况下应该用 useCallback 包裹回调函数?
🔍 追问:
- "这个场景需要 useCallback 吗?"
const handleClick = () => console.log('click');
<Button onClick={handleClick} />
- "依赖数组应该包含哪些值?"
- "useCallback 的性能开销有多大?"
7. 懒加载和代码分割怎么做?React.lazy 的使用场景?
🔍 追问:
- "React.lazy 的原理是什么?"
- "如何预加载路由组件?"
- "懒加载失败如何处理?"
- "Suspense 的 fallback 如何设计?"
💻 可能的现场写:
const LazyComponent = lazy(() => import('./Heavy'));
function App() {
return (
<Suspense fallback={<Loading />}>
<LazyComponent />
</Suspense>
);
}
📌 状态管理(3题)
1. Context 的性能问题是什么?如何优化?
🔍 追问:
- "为什么 Context 会导致所有消费者重渲染?"
- "如何拆分 Context 避免性能问题?"
- "useMemo 可以解决 Context 重渲染吗?"
- "现场优化这段代码:"
const value = { user, theme, settings };
<AppContext.Provider value={value}>
💻 可能的现场写:
// Context 拆分优化
const UserContext = createContext();
const ThemeContext = createContext();
// 或使用 useMemo
const value = useMemo(() => ({ user, theme }), [user, theme]);
4. 如何避免 prop drilling?有哪些解决方案?
🔍 追问:
- "什么情况下 prop drilling 是可以接受的?"
- "Context、状态提升、组合模式,如何选择?"
- "children props 如何避免 prop drilling?"
6. useReducer 什么时候用?和 useState 的区别?
🔍 追问:
- "什么样的状态适合用 useReducer?"
- "现场实现一个复杂表单的 reducer"
- "useReducer 和 Redux 的区别?"
💻 可能的现场写:
const [state, dispatch] = useReducer((state, action) => {
switch (action.type) {
case 'SET_FIELD':
return { ...state, [action.field]: action.value };
case 'RESET':
return initialState;
default:
return state;
}
}, initialState);
📌 数据获取(3题)
1. 如何在 React 中获取数据?useEffect 里直接 fetch 有什么问题?
🔍 追问:
- "为什么这段代码有问题?"
useEffect(() => {
fetch(url).then(res => res.json()).then(setData);
}, [url]);
- "如何处理竞态条件?"
- "如何处理 loading 和 error 状态?"
- "现场实现一个完整的数据获取逻辑"
💻 可能的现场写:
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let cancelled = false;
const controller = new AbortController();
fetch(url, { signal: controller.signal })
.then(res => res.json())
.then(data => {
if (!cancelled) setData(data);
})
.catch(err => {
if (!cancelled) setError(err);
})
.finally(() => {
if (!cancelled) setLoading(false);
});
return () => {
cancelled = true;
controller.abort();
};
}, [url]);
return { data, loading, error };
}
2. 如何取消正在进行的请求?AbortController 怎么用?
🔍 追问:
- "为什么需要取消请求?"
- "如何处理快速切换 tab 的场景?"
- "axios 如何取消请求?"
- "取消后还能设置状态吗?"
3. React Query/SWR 解决了什么问题?和直接 fetch 的区别?
🔍 追问:
- "缓存策略是怎样的?"
- "stale-while-revalidate 是什么意思?"
- "乐观更新如何实现?"
- "什么时候需要用这些库?"
⭐ Level 2: 高频实战题(15题)
📌 事件与交互(4题)
1. React 的事件系统和原生事件的区别?什么是事件委托?
🔍 追问:
- "React 17 的事件系统有什么变化?"
- "e.stopPropagation() 能阻止原生事件吗?"
- "如何同时使用 React 事件和原生事件?"
- "为什么 React 不直接用原生事件?"
3. 如何实现防抖和节流?在 React 中使用有什么注意事项?
🔍 追问:
- "现场实现 useDebounce 和 useThrottle"
- "为什么不能直接用 lodash.debounce?"
- "依赖数组如何处理?"
💻 必须现场写:
function useDebounce(callback, delay) {
const callbackRef = useRef(callback);
useEffect(() => {
callbackRef.current = callback;
}, [callback]);
return useMemo(() => {
return debounce((...args) => {
callbackRef.current(...args);
}, delay);
}, [delay]);
}
4. 如何处理文件上传?包括进度显示、预览等?
🔍 追问:
- "如何获取上传进度?"
- "如何实现图片预览?"
- "大文件上传如何优化(分片上传)?"
- "如何做拖拽上传?"
💻 可能的现场写:
function FileUpload() {
const [progress, setProgress] = useState(0);
const [preview, setPreview] = useState('');
const handleFileChange = (e) => {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = (e) => setPreview(e.target.result);
reader.readAsDataURL(file);
// 上传逻辑
const formData = new FormData();
formData.append('file', file);
axios.post('/upload', formData, {
onUploadProgress: (e) => {
setProgress((e.loaded / e.total) * 100);
}
});
};
return (
<>
<input type="file" onChange={handleFileChange} />
{preview && <img src={preview} />}
{progress > 0 && <progress value={progress} max={100} />}
</>
);
}
6. 如何实现全局快捷键?如何避免冲突?
🔍 追问:
- "如何监听组合键(Ctrl+S)?"
- "如何处理输入框中的快捷键?"
- "如何实现快捷键的优先级?"
- "现场实现一个 useKeyboard Hook"
💻 可能的现场写:
function useKeyPress(targetKey) {
const [keyPressed, setKeyPressed] = useState(false);
useEffect(() => {
const downHandler = ({ key }) => {
if (key === targetKey) setKeyPressed(true);
};
const upHandler = ({ key }) => {
if (key === targetKey) setKeyPressed(false);
};
window.addEventListener('keydown', downHandler);
window.addEventListener('keyup', upHandler);
return () => {
window.removeEventListener('keydown', downHandler);
window.removeEventListener('keyup', upHandler);
};
}, [targetKey]);
return keyPressed;
}
📌 列表与滚动(2题)
1. 列表渲染为什么需要 key?用 index 作为 key 有什么问题?
🔍 追问:
- "为什么这个列表的顺序会错乱?"
{items.map((item, index) => (
<input key={index} defaultValue={item} />
))}
- "什么时候可以用 index 作为 key?"
- "key 的作用原理是什么?"
- "动态列表的 key 如何选择?"
6. 无限滚动(Infinite Scroll)如何实现?
🔍 追问:
- "如何检测滚动到底部?"
- "Intersection Observer 如何使用?"
- "如何避免重复加载?"
- "现场实现一个 useInfiniteScroll Hook"
💻 可能的现场写:
function useInfiniteScroll(callback) {
const loaderRef = useRef(null);
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting) {
callback();
}
},
{ threshold: 1.0 }
);
if (loaderRef.current) {
observer.observe(loaderRef.current);
}
return () => observer.disconnect();
}, [callback]);
return loaderRef;
}
📌 表单处理(2题)
2. 如何在 React 中处理表单?有哪些表单库可以用?
🔍 追问:
- "受控组件和非受控组件如何选择?"
- "React Hook Form 和 Formik 的区别?"
- "现场实现一个表单验证"
- "如何处理表单的 dirty 状态?"
💻 可能的现场写:
function Form() {
const { register, handleSubmit, formState: { errors } } = useForm();
const onSubmit = (data) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input
{...register('email', {
required: 'Email is required',
pattern: {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+.[A-Z]{2,}$/i,
message: 'Invalid email'
}
})}
/>
{errors.email && <span>{errors.email.message}</span>}
<button type="submit">Submit</button>
</form>
);
}
3. 受控组件和非受控组件的区别?什么时候用非受控?
🔍 追问:
- "为什么推荐用受控组件?"
- "文件上传为什么必须用非受控?"
- "如何用 ref 获取非受控组件的值?"
- "useRef 和 useState 在表单中的区别?"
📌 路由相关(3题)
3. 如何实现路由守卫?如何处理未登录跳转?
🔍 追问:
- "现场实现一个 ProtectedRoute 组件"
- "如何记住用户想访问的页面?"
- "权限检查应该放在哪里?"
💻 必须现场写:
function ProtectedRoute({ children }) {
const { user } = useAuth();
const location = useLocation();
if (!user) {
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
}
// 使用
<Route path="/dashboard" element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
} />
4. 如何在路由跳转前确认?(比如表单未保存提示)
🔍 追问:
- "React Router v6 如何实现?"
- "useBlocker 如何使用?"
- "浏览器关闭时如何提示?"
- "现场实现表单离开确认"
💻 可能的现场写:
function usePrompt(message, when) {
const blocker = useBlocker(when);
useEffect(() => {
if (blocker.state === 'blocked') {
if (window.confirm(message)) {
blocker.proceed();
} else {
blocker.reset();
}
}
}, [blocker, message]);
useEffect(() => {
if (when) {
window.onbeforeunload = () => message;
return () => { window.onbeforeunload = null; };
}
}, [when, message]);
}
6. 路由状态如何保存和恢复?(比如列表页滚动位置)
🔍 追问:
- "如何保存滚动位置?"
- "返回列表页如何恢复状态?"
- "sessionStorage 还是内存中保存?"
- "如何实现路由缓存(Keep Alive)?"
📌 组件设计(2题)
3. 受控组件和非受控组件的区别?什么时候用非受控?
(已在表单处理中)
4. 如何设计一个可复用的组件?需要考虑哪些方面?
🔍 追问:
- "现场设计一个 Modal 组件的 API"
- "如何设计组件的 props?"
- "如何处理默认值?"
- "如何让组件既可以受控也可以非受控?"
💻 可能的现场写:
// 设计一个灵活的 Select 组件
function Select({
value,
onChange,
defaultValue,
options,
disabled,
placeholder
}) {
const [internalValue, setInternalValue] = useState(defaultValue);
const isControlled = value !== undefined;
const currentValue = isControlled ? value : internalValue;
const handleChange = (newValue) => {
if (!isControlled) {
setInternalValue(newValue);
}
onChange?.(newValue);
};
// 实现
}
📌 并发特性(2题)
2. useTransition 和 useDeferredValue 的使用场景?
🔍 追问:
- "什么时候用 useTransition?什么时候用 useDeferredValue?"
- "现场优化一个搜索框的卡顿问题"
- "isPending 如何使用?"
💻 可能的现场写:
function SearchResults({ query }) {
const [isPending, startTransition] = useTransition();
const [results, setResults] = useState([]);
useEffect(() => {
startTransition(() => {
// 耗时的搜索操作
const filtered = largeList.filter(item =>
item.includes(query)
);
setResults(filtered);
});
}, [query]);
return (
<div>
{isPending && <Spinner />}
<ul>{results.map(...)}</ul>
</div>
);
}
3. Suspense 如何使用?和 loading 状态有什么区别?
🔍 追问:
- "Suspense 的工作原理?"
- "ErrorBoundary 和 Suspense 如何配合?"
- "多个 Suspense 如何协调?"
- "SuspenseList 是什么?"
⭐ Level 3: 加分题(10题)
📌 架构设计(3题)
3. 前端如何做错误边界(Error Boundary)?如何上报错误?
🔍 追问:
- "现场实现一个 ErrorBoundary 组件"
- "如何区分可恢复和不可恢复的错误?"
- "如何上报错误到监控平台?"
- "Hooks 中的错误如何捕获?"
💻 必须现场写:
class ErrorBoundary extends React.Component {
state = { hasError: false, error: null };
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
// 上报错误
logErrorToService(error, errorInfo);
}
render() {
if (this.state.hasError) {
return <ErrorFallback error={this.state.error} />;
}
return this.props.children;
}
}
4. 如何实现路由权限控制?
🔍 追问:
- "路由级别和组件级别的权限如何结合?"
- "动态路由如何生成?"
- "权限变化时如何处理?"
- "现场设计一个权限系统"
5. 如何做前端监控和性能埋点?
🔍 追问:
- "需要监控哪些指标?"
- "如何采集用户行为?"
- "Performance API 如何使用?"
- "如何实现曝光埋点?"
📌 调试问题(3题)
1. 组件为什么会重复渲染?如何定位原因?
🔍 追问:
- "现场用 React DevTools Profiler 分析"
- "why-did-you-render 如何使用?"
- "常见的导致重渲染的原因有哪些?"
- "如何打印渲染次数?"
💻 可能的现场写:
function useRenderCount() {
const count = useRef(0);
useEffect(() => {
count.current++;
console.log(`Rendered ${count.current} times`);
});
}
4. 闭包陷阱(Stale Closure)是什么?如何避免?
🔍 追问:
- "为什么 setInterval 里的 state 是旧值?"
- "如何用 useRef 解决?"
- "useCallback 的依赖为什么重要?"
- "现场修复这段代码:"
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCount(count + 1); // Bug!
}, 1000);
return () => clearInterval(timer);
}, []);
💻 必须现场写:
// 解决方案1:函数式更新
setCount(c => c + 1);
// 解决方案2:useRef
const countRef = useRef(count);
countRef.current = count;
setCount(countRef.current + 1);
// 解决方案3:添加依赖
useEffect(() => {
// ...
}, [count]);
5. 如何调试 React 性能问题?Profiler API 怎么用?
🔍 追问:
- "Chrome Performance 如何分析 React?"
- "React DevTools Profiler 的各个指标含义?"
- "如何找到性能瓶颈?"
- "Profiler 组件如何使用?"
📌 实际业务(4题)
1. 如何实现一个搜索框(包括防抖、高亮、历史记录)?
🔍 追问:
- "现场实现完整功能"
- "如何高亮匹配的文字?"
- "历史记录如何存储?"
- "如何处理搜索建议?"
💻 必须现场写(核心逻辑)
2. 如何实现一个无限滚动的瀑布流?
🔍 追问:
- "瀑布流布局如何计算?"
- "如何优化图片加载?"
- "如何处理不同高度的卡片?"
- "Masonry 库的原理?"
3. 如何实现一个拖拽排序的看板(Kanban)?
🔍 追问:
- "拖拽库如何选择(react-dnd vs dnd-kit)?"
- "如何实现跨列表拖拽?"
- "如何实现乐观更新?"
- "拖拽时的视觉反馈如何实现?"
7. 如何实现购物车功能?
🔍 追问:
- "状态如何设计?"
- "本地存储如何同步?"
- "库存不足如何处理?"
- "现场设计购物车的数据结构和操作"
💻 可能的现场写:
function useCart() {
const [items, setItems] = useState(() => {
return JSON.parse(localStorage.getItem('cart') || '[]');
});
useEffect(() => {
localStorage.setItem('cart', JSON.stringify(items));
}, [items]);
const addItem = (product) => {
setItems(items => {
const existing = items.find(i => i.id === product.id);
if (existing) {
return items.map(i =>
i.id === product.id
? { ...i, quantity: i.quantity + 1 }
: i
);
}
return [...items, { ...product, quantity: 1 }];
});
};
const removeItem = (id) => {
setItems(items => items.filter(i => i.id !== id));
};
const total = items.reduce((sum, item) =>
sum + item.price * item.quantity, 0
);
return { items, addItem, removeItem, total };
}
📊 优先级总结
🔥 必刷(必考 + 可以现场写代码)
- 自定义 Hook(useDebounce, useLocalStorage)
- 数据获取 + 竞态处理
- 防抖节流在 React 中的实现
- 文件上传(进度、预览)
- 无限滚动实现
- 路由守卫实现
- 表单验证
- ErrorBoundary
- 闭包陷阱修复
- 购物车实现
⚡ 高频(80%面试会问)
- useEffect 依赖数组、清理函数
- useCallback/useMemo 使用场景
- Context 性能优化
- 列表 key 的作用
- 虚拟滚动原理
- React 事件系统
- 组件重渲染原因
💡 加分(展示深度)
- React 18 并发特性
- SSR/RSC 理解
- 性能调试方法
- 复杂业务场景实现
建议每天重点练习 2-3 道带代码实现的题目,3个月可以掌握所有核心场景!