1、useEffect 与 useLayoutEffect 有什么区别?执行时机分别是什么?
答:useEffect在DOM更新并绘制后异步执行,不会阻塞渲染;useLayoutEffect在DOM更新后、浏览器绘制前同步执行,常用于测量DOM或避免闪烁。一般推荐用useEffect,只有在需要精确DOM操作时才用useLayoutEffect。
- 浏览器渲染流程:
React更新虚拟DOM→ 提交更新,修改真实DOM→useLayoutEffect执行(同步,阻塞后续绘制)→ 浏览器进行绘制(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)传递,子组件通过回调影响父状态。这种机制保证了数据的可控性和可预测性。当状态复杂时,可以通过状态提升、Context、Redux等状态管理库 来解决。
- 复杂状态处理:当状态跨组件、跨层级传递复杂时,可以用:
- 状态提升(
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 性能优化的常见手段有哪些?比如避免不必要的渲染
- 避免不必要的渲染
React.memo:用于函数组件,只有props发生变化时才重新渲染。
const Child = React.memo(({value})=>{
console.log('渲染了');
return <div>{value}</div>;
})
useCallback:缓存函数引用,避免子组件因为props引用变化而重复渲染。useMemo:缓存计算结果,避免重复计算shouldComponentUpdate / PureComponent:类组件优化手段,PureComponent内部做了props和state的浅比较- 避免匿名函数和对象字面量,因为每次
render都会生成新引用 → 导致子组件重新渲染
// 错误❎
<Child onClick={() => setCount(count+1)} />
// 正确✅
const handleClick = useCallback(...);
<Child onClick={handleClick} />
- 渲染层级优化
- 组件拆分:把大组件拆分为小组件,避免局部状态更新引起整棵树重渲染。
- 列表渲染优化:加上
key, - 使用虚拟列表(
react-window、react-virtualized)
- 状态管理优化
- 避免状态提升过度,把只属于局部的
state放在子组件 - 使用
Context时避免全局re-render- 解决办法:拆分
Context;使用use-context-selector或zustand、jotai这类更细粒度的状态管理库
- 解决办法:拆分
- 渲染流程优化
- 懒加载 & 按需加载
React.lazy + Suspense实现路由懒加载Webpack/Vite代码分割SSR/SSG使用Next.js进行服务端渲染/预渲染,减少客户端计算
- 浏览器绘制优化
- 尽量只让
CSS动画使用transform和opacity - 避免大面积
reflow/repaint - 使用
requestAnimationFrame控制JS动画