2026 React前端面试常考

83 阅读3分钟

最常问的初级前端问题:

React的diff算法和复杂度

  1. 同层比较
  2. 类型判断:不同type直接卸载旧子树,挂载新的子树;相同的type,就复用节点实例,递归对比children;
  3. 列表diff:
    • 有稳定的key:通过key建立匹配与移动、插入、删除的决策。这是比较的算法复杂度在O(n)的关键。
    • 没有稳定的key:会有性能问题

react的常用hook

  • 状态与数据流
    • useState:局部状态
    • useRender:复杂状态机
    • useContext:跨层级依赖注入
  • 副作用与生命周期
    • useEffect:异步副作用
    • useLayoutEfffect:在浏览器paint前同步执行
  • 引用和示例
    • useRef:持久化可变容器,保存dom引用
  • 并发体验
    • useTransition:将非紧急更新标记为低优先级,避免卡交互;

React用于性能优化的hook

  1. 减少重复计算 、稳定引用
    • useMemo,缓存计算结果
    • useCallback:缓存函数引用
  2. useRef:缓存不需要驱动UI的可变值
  3. React 18+的hook:并发、交互体验
    • useTransition 、startTransition:把非紧急更新标记为低优先级
    • useDefferedValue:把某个值延迟给昂贵渲染用,保持输入更丝滑

React的useRef解决rereder问题、闭包问题

  • 保存可变值(不触发rerender)

      const countRef = useRef(0);
      countRef.current +=1 ;
      console.log(countRef.curent) //会变成2,但是不会触发组件rerender
    

-解决闭包旧值的问题 (定时器、订阅回调里获取最新的值)

function demo(){
    const [count,setCount] = useState(0);
    const countRef = useRef(count);

    useEffect(()=>{
        countRef.current = count;
    },[count]);
    
    useEffect(()=>{
        const id = setInterval(()=>{
            console.log("latest",countRef.current);
        },1000);
        return()=>clearInterval(id);
    },[]); //只在挂载后执行一次,卸载时执行cleanUp
}
  • 获取DOM节点进行操作

      function InputFocus(){
          const inputRef = useRef(null);
          useEffect(()=>{
              inputRef.current?.focus();//挂载后自动聚焦一次
          },[])
    
          return <input ref={inputRef} />
      }
    

React的Fiber

  • 定义:Fiber是React 16引入的核心架构重写:把一次渲染工作拆成很多可中断的,可恢复的,可复用的Fiber节点,可以对不同的fiber node去分配不同的优先级,必要时可以暂停低优先级的任务,保证交互的流畅。fiber的目标是提升对动画,手势,布局等场景的适配能力。
  • 注意:Fiber是让渲染阶段具体“可打断性”,但commit阶段为了保证一致性,通常不可随意中断

React 18 - 19发生了哪些更新

  1. 并发基础
    • 更多场景默认批处理state更新,减少重复渲染
    • 增加useTransition用于区别紧急与非紧急更新
    • streamSSR + suspense:支持流式服务端渲染与suspense协作
  2. 围绕“Action”异步提交、表单、乐观更新的能力增强
    • Action约定
    • 增加hooks、API

React的渲染机制

  • 渲染机制定义

      1. Schedule调度:在状态更新(setState),props变化,Context变化等会调度一次更新
      1. Render渲染阶段:react生成函数组件,生成新的react element tree,做diff算法,计算需要的dom变更
      1. commit提交:将变更应用到DOM上。DOM变更后,useLayoutEffect同步执行,在浏览器paint之前;paint后执行useEffect;
  • 代码阅读题(看代码说明react的渲染机制)

  • 一次点击后发生了什么

      function App() {
        const [n, setN] = React.useState(0);
    
        console.log("render", n);
    
        React.useEffect(() => {
          console.log("effect", n);
          return () => console.log("cleanup", n);
        }, [n]);
    
        return (
          <button
            onClick={() => {
              console.log("click");
              setN(n + 1);
              console.log("after setN");
            }}
          >
            +
          </button>
        );
      }
      
    

    1.首次挂载: 2.render 0 3.effective 0 4.点击一次 5.click 6.after setN 7.render 1 8.(commit阶段后)cleanUp 0 9.(paint阶段后)effect 1

  • 连写两次setState为什么只加1,怎么加2

      function App() {
        const [n, setN] = React.useState(0);
        console.log("render", n);
    
        return (
          <button onClick={() => {
            setN(n + 1);
            setN(n + 1);
          }}>
            +
          </button>
        );
      }
      
      
    

日志输出:render 0 - 点击后 - render 1 (不会render两次,因为被batching合并了) 修改使用到的都是同一个闭包的旧值

setN(x => x + 1);
setN(x => x + 1);
  • 父组件更新,子组件也rerender,React.memo没拦住

      const Child = React.memo(function Child({ onInc }) {
       console.log("Child render");
       return <button onClick={onInc}>child</button>;
     });
    
     function Parent() {
       const [n, setN] = React.useState(0);
       console.log("Parent render");
    
       const onInc = () => setN(n + 1);
    
       return (
         <>
           <div>{n}</div>
           <Child onInc={onInc} />
           <button onClick={() => setN(n + 1)}>parent+</button>
         </>
       );
     }
    

日志输出: Parent render - Child render - 点击“parent+” - onInc引用发生改变(react.memo是浅比较)

如何修复

const onInc = React.useCallback(() => {
  setN(x => x + 1);
}, []);