React面试题

62 阅读8分钟

⭐ 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 };
}

📊 优先级总结

🔥 必刷(必考 + 可以现场写代码)

  1. 自定义 Hook(useDebounce, useLocalStorage)
  2. 数据获取 + 竞态处理
  3. 防抖节流在 React 中的实现
  4. 文件上传(进度、预览)
  5. 无限滚动实现
  6. 路由守卫实现
  7. 表单验证
  8. ErrorBoundary
  9. 闭包陷阱修复
  10. 购物车实现

⚡ 高频(80%面试会问)

  • useEffect 依赖数组、清理函数
  • useCallback/useMemo 使用场景
  • Context 性能优化
  • 列表 key 的作用
  • 虚拟滚动原理
  • React 事件系统
  • 组件重渲染原因

💡 加分(展示深度)

  • React 18 并发特性
  • SSR/RSC 理解
  • 性能调试方法
  • 复杂业务场景实现

建议每天重点练习 2-3 道带代码实现的题目,3个月可以掌握所有核心场景!