高频场景题
把 Hooks / 渲染 / 并发串成 可落地的小题。面试常让你口述思路或手写关键片段。
1. 防抖搜索
需求:输入停顿后再请求;快速输入不要打爆接口;切换关键词要取消旧请求。
function useDebouncedValue<T>(value: T, delay = 300) {
const [v, setV] = useState(value)
useEffect(() => {
const t = setTimeout(() => setV(value), delay)
return () => clearTimeout(t)
}, [value, delay])
return v
}
function SearchBox() {
const [q, setQ] = useState('')
const debounced = useDebouncedValue(q, 300)
const [result, setResult] = useState<string[]>([])
useEffect(() => {
if (!debounced) {
setResult([])
return
}
const ac = new AbortController()
fetch(`/api/search?q=${encodeURIComponent(debounced)}`, {
signal: ac.signal,
})
.then((r) => r.json())
.then(setResult)
.catch((e) => {
if (e.name !== 'AbortError') throw e
})
return () => ac.abort()
}, [debounced])
return <input value={q} onChange={(e) => setQ(e.target.value)} />
}
考点:防抖 cleanup、AbortController 竞态、受控输入。
2. 请求竞态(后发先至)
只保留最后一次请求结果:
useEffect(() => {
let cancelled = false
load(id).then((data) => {
if (!cancelled) setData(data)
})
return () => {
cancelled = true
}
}, [id])
或统一 AbortController。面试要主动提「慢请求覆盖新数据」问题。
3. 无限滚动(口述即可)
- 哨兵 DOM +
IntersectionObserver page/cursorstate;loading / hasMore 防重入- 卸载时
observer.disconnect() - 列表用稳定 key;很长则虚拟列表
4. 表单:受控 vs 非受控
| 受控 | 非受控 | |
|---|---|---|
| 数据在哪 | React state | DOM(ref) |
| 适合 | 校验、联动、禁用提交 | 简单表单、文件、少干预 |
| 成本 | 每键一次 setState | 提交时读一次 |
// 受控
const [name, setName] = useState('')
<input value={name} onChange={(e) => setName(e.target.value)} />
// 非受控
const ref = useRef<HTMLInputElement>(null)
<input defaultValue="初始" ref={ref} />
// submit: ref.current?.value
5. 子组件如何少渲染
场景:父有计数器,旁路有重列表。
思路链(按优先级说):
- 状态拆分 / 下沉,别让计数器和列表共一个父 state 硬绑
- 列表
memo, 回调useCallback或放 store - 大过滤丢进
startTransition - 仍卡 → 虚拟列表
6. 自定义 Hook 设计题
「封装一个 useLocalStorage(key, initial)」:
useState读初始(注意 SSR:window不存在)set时同步localStorage- 可选:监听
storage事件做跨标签同步 - 用 try/catch 防隐私模式配额错误
7. 为什么列表闪一下 / 状态串行
常查:
key用了 index 且列表会插入删除- 请求无取消,旧数据回来把新 UI 盖掉
useEffect里 setState 造成「先空后满」——可用占位或保留上一次数据直到新数据到
8. 口述题速答
Q:怎么做权限路由?
鉴权结果进 store/context;路由层根据 role 重定向;注意首屏 token 异步校验时的 loading,避免闪登录页。
Q:错误边界能捕 hook 里的异步错误吗?
不能直接捕 async;Error Boundary 捕的是渲染期/生命周期里的同步抛错。异步要在 promise catch 里设 error state,或再 throw 到可被边界接住的路径(需谨慎设计)。
Q:SSR 注意点?
首屏 HTML 与客户端 hydration 一致;useEffect 只在客户端跑;localStorage / window 别在 render 直接碰。
面试表达建议
- 先复述需求与约束(性能、取消、边界)
- 说数据放哪(state / ref / store)
- 说副作用与清理
- 再提优化与坑
相关:Hooks 核心与常见坑 · 状态管理