Skip to main content

高频场景题

把 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. 无限滚动(口述即可)

  1. 哨兵 DOM + IntersectionObserver
  2. page / cursor state;loading / hasMore 防重入
  3. 卸载时 observer.disconnect()
  4. 列表用稳定 key;很长则虚拟列表

4. 表单:受控 vs 非受控

受控非受控
数据在哪React stateDOM(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. 子组件如何少渲染

场景:父有计数器,旁路有重列表。

思路链(按优先级说):

  1. 状态拆分 / 下沉,别让计数器和列表共一个父 state 硬绑
  2. 列表 memo,回调 useCallback 或放 store
  3. 大过滤丢进 startTransition
  4. 仍卡 → 虚拟列表

(细节见渲染机制与性能优化并发特性

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 直接碰。

面试表达建议

  1. 先复述需求与约束(性能、取消、边界)
  2. 说数据放哪(state / ref / store)
  3. 说副作用与清理
  4. 再提优化与坑

相关:Hooks 核心与常见坑 · 状态管理