面试备战录

97 阅读4分钟

1、useEffect 与 useLayoutEffect 有什么区别?执行时机分别是什么?

答:useEffectDOM更新并绘制后异步执行,不会阻塞渲染;useLayoutEffectDOM更新后、浏览器绘制前同步执行,常用于测量DOM或避免闪烁。一般推荐用useEffect,只有在需要精确DOM操作时才用useLayoutEffect

  • 浏览器渲染流程:React更新虚拟DOM → 提交更新,修改真实DOMuseLayoutEffect执行(同步,阻塞后续绘制)→ 浏览器进行绘制(Paint)→ useEffect执行(异步,不影响绘制)。
  • 使用场景
    • useEffect:异步逻辑、数据请求;事件监听、订阅;打点上报。
    • useLayoutEffect(需要阻塞绘制时用):操作DOM布局(测量元素尺寸、计算位置);手动触发动画,避免闪烁。

2、useCallback 和 useMemo 的区别?何时使用?

答:useCallback用于缓存函数引用,避免函数作为props传递时引起子组件无意义的重新渲染。useMemo用于缓存计算结果避免重复执行开销大的计算或保持对象/数组引用稳定。二者都是性能优化工具,实际开发中应按需使用,不要过度优化(因为维护依赖数组本身也有成本)。

  • useCallback(fn, deps):返回的是 函数的memoized引用。适合优化函数组件中避免函数重复创建,常用于props传递回调。
    • React默认每次渲染都会生成新的函数对象。
    • 如果把函数传给子组件(props),可能导致子组件不必要的重新渲染。
    • useCallback可以缓存函数引用,只有依赖变化时才会重新生成。
const memoizedFn = useCallback(()=>{
  console.log('only recreate when deps change');
},[deps])
  • useMemo(factory, deps):返回的是 计算结果的memoized 值。适合优化复杂计算,避免每次渲染都重复执行开销大的逻辑。
    • 有些计算逻辑开销大(如循环、复杂运算)。
    • useMemo缓存计算结果,避免每次渲染都重新计算。
    • 只有依赖变化时才会重新计算。
const memoizedValue = useMemo(()=>{
  return expensiveComputation(data);
},[data])

3、为什么说 React 是单向数据流?如何处理复杂状态?

答:React是单向数据流,数据只能自上而下(props)传递,子组件通过回调影响父状态。这种机制保证了数据的可控性和可预测性。当状态复杂时,可以通过状态提升ContextRedux等状态管理库 来解决。

  • 复杂状态处理:当状态跨组件、跨层级传递复杂时,可以用:
    • 状态提升(Lifting State Up):把公共状态提升到最近的父组件,由父组件统一管理。
    • Context API(跨层级共享):解决“props层层传递”问题。
    import { createContext } from 'react';
    const UserContext = createContext();
    function App(){
      const [user, setUser] = useState('尊敬的大人');
      return(
      <UserContext.Provider value={user}>
        <Profile />
      </UserContext.Provider>    
      )
    }
    function Profile() {
      const user = useContext(UserContext);
      return <p>{user}</p>;
    }
    
    • 第三方状态管理(Redux、MobX、Zustand、Recoil、Jotai等)
      • 当应用规模大时,需要集中管理全局状态。
      • Redux提供可预测的单一状态树;Zustand更轻量;Recoil/Jotai则更贴近Hooks思维。
  • 为什么是单向数据流?
    • 保证数据可控、可预测
    • 数据修改来源单一(state/setState),减少副作用。
    • 避免像双向绑定那样“谁改了数据”的混乱。
  • 为什么需要额外状态管理?
    • 当组件树很深时,props逐层传递会造成prop drilling(属性层层下钻)。
    • 当多个组件需要共享状态时(如用户信息、购物车),管理和同步会很复杂。

4、React 性能优化的常见手段有哪些?比如避免不必要的渲染

  1. 避免不必要的渲染
  • React.memo:用于函数组件,只有props发生变化时才重新渲染。
const Child = React.memo(({value})=>{
  console.log('渲染了');
  return <div>{value}</div>;
})
  • useCallback:缓存函数引用,避免子组件因为props引用变化而重复渲染。
  • useMemo:缓存计算结果,避免重复计算
  • shouldComponentUpdate / PureComponent:类组件优化手段,PureComponent内部做了propsstate的浅比较
  • 避免匿名函数和对象字面量,因为每次render都会生成新引用 → 导致子组件重新渲染
// 错误❎
<Child onClick={() => setCount(count+1)} />
// 正确✅
const handleClick = useCallback(...);
<Child onClick={handleClick} />
  1. 渲染层级优化
  • 组件拆分:把大组件拆分为小组件,避免局部状态更新引起整棵树重渲染。
  • 列表渲染优化:加上key
  • 使用虚拟列表react-window、react-virtualized
  1. 状态管理优化
  • 避免状态提升过度,把只属于局部的state放在子组件
  • 使用Context时避免全局re-render
    • 解决办法:拆分Context;使用use-context-selectorzustand、jotai这类更细粒度的状态管理库
  1. 渲染流程优化
  • 懒加载 & 按需加载
    • React.lazy + Suspense 实现路由懒加载
    • Webpack/Vite 代码分割
    • SSR/SSG使用Next.js进行服务端渲染/预渲染,减少客户端计算
  1. 浏览器绘制优化
  • 尽量只让CSS动画使用transformopacity
  • 避免大面积reflow/repaint
  • 使用requestAnimationFrame控制JS动画