React Hooks常见面试题1

182 阅读5分钟

温故知新

useMemouseCallback

useMemouseCallback 是 React Hooks,用于优化函数组件的性能。尽管它们功能相似,但适用场景和作用不同。


1. useMemo

用途

  • 用于 缓存计算结果
  • 避免在每次渲染时重复执行昂贵的计算。

签名

const memoizedValue = useMemo(() => computeValue, [dependency]);
  • computeValue: 一个函数,返回要缓存的值。
  • dependency: 依赖项数组,只有在依赖发生变化时,computeValue 才会重新执行。

适用场景

  • 复杂计算: 如过滤、排序、或其他需要大量计算的逻辑。
  • 避免不必要的渲染: 当需要将计算结果作为子组件的 prop 传递时。

示例

import React, { useMemo, useState } from 'react';

const ExpensiveComponent = ({ numbers }) => {
    const sum = useMemo(() => {
        console.log('Calculating sum...');
        return numbers.reduce((total, num) => total + num, 0);
    }, [numbers]);

    return <div>Sum: {sum}</div>;
};

const App = () => {
    const [count, setCount] = useState(0);
    const numbers = [1, 2, 3, 4, 5];

    return (
        <div>
            <ExpensiveComponent numbers={numbers} />
            <button onClick={() => setCount(count + 1)}>Increment: {count}</button>
        </div>
    );
};

输出:

  • 只有当 numbers 改变时,Calculating sum... 会执行。

2. useCallback

用途

  • 用于 缓存函数引用
  • 避免函数在每次渲染时被重新创建。

签名

const memoizedCallback = useCallback(() => doSomething, [dependency]);
  • doSomething: 要缓存的回调函数。
  • dependency: 依赖项数组,只有在依赖发生变化时,回调函数才会被重新创建。

适用场景

  • 防止子组件不必要的渲染: 当子组件通过 React.memo 优化且依赖于父组件传递的回调函数。
  • 事件处理函数: 缓存事件处理函数,避免每次渲染都生成新的函数。

示例

import React, { useCallback, useState } from 'react';

const Child = React.memo(({ onClick }) => {
    console.log('Child rendered');
    return <button onClick={onClick}>Click me</button>;
});

const App = () => {
    const [count, setCount] = useState(0);

    const handleClick = useCallback(() => {
        console.log('Button clicked');
    }, []);

    return (
        <div>
            <Child onClick={handleClick} />
            <button onClick={() => setCount(count + 1)}>Increment: {count}</button>
        </div>
    );
};

输出:

  • Child rendered 只在初次渲染时执行,或 handleClick 引用发生变化时执行。

3. 区别总结

特性useMemouseCallback
作用缓存计算结果缓存函数引用
返回值计算结果(如一个值、对象、数组)缓存的函数
适用场景避免重复计算避免函数重新创建
依赖项变化时行为重新计算并返回新的结果返回新的函数引用

4. 何时使用

  • 使用 useMemo:

    • 当需要优化复杂计算的性能。
    • 当计算结果影响子组件的渲染。
  • 使用 useCallback:

    • 当需要优化子组件的性能,尤其是子组件通过 React.memo 包裹时。
    • 当函数作为子组件 prop 传递,并希望避免函数重新创建。

注意事项

  1. 不要过度使用 useMemouseCallback,如果性能优化无显著收益,会增加代码复杂度。
  2. 如果计算或函数轻量级,不需要提前优化。
  3. 两者都依赖 依赖项数组,需要确保依赖项设置正确,避免引发错误或性能问题。

useEffectuseLayoutEffect

在 React 中,useEffectuseLayoutEffect 都是用来处理副作用的 Hook,但它们的执行时机不同,适用于不同的场景。以下是它们的区别和适用场景的详细说明:


1. 执行时机

useEffect

  • 执行时机: 在浏览器完成渲染后(即 异步)。
  • 特点: 不会阻塞浏览器绘制,副作用的执行与渲染解耦。
  • 适用场景:
    • 不影响页面布局的副作用。
    • 例如:数据获取(fetch)、订阅事件、设置计时器。

useLayoutEffect

  • 执行时机: 在浏览器执行绘制之前(即 同步)。
  • 特点: 会阻塞浏览器绘制,副作用的执行在 DOM 更新后、绘制前。
  • 适用场景:
    • 需要在 DOM 完成更新后立即操作 DOM。
    • 例如:测量 DOM 元素尺寸或位置、动态调整样式。

2. 代码执行顺序

  • useEffect 顺序:

    1. React 渲染 DOM。
    2. 浏览器完成绘制。
    3. useEffect 中的代码执行。
  • useLayoutEffect 顺序:

    1. React 渲染 DOM。
    2. useLayoutEffect 中的代码执行。
    3. 浏览器绘制。

3. 示例对比

useEffect 示例

以下代码设置一个计时器,更新页面内容:

import React, { useState, useEffect } from "react";

const Example = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log("useEffect: DOM 已更新,页面渲染完成。");
    const timer = setInterval(() => setCount((c) => c + 1), 1000);
    return () => clearInterval(timer); // 清理副作用
  }, []);

  return <div>Count: {count}</div>;
};
export default Example;
  • 关键点: useEffect 中的代码在页面完成渲染后运行,不影响页面的首次绘制。

useLayoutEffect 示例

以下代码测量 DOM 元素的宽度,并在绘制前更新样式:

import React, { useRef, useLayoutEffect, useState } from "react";

const Example = () => {
  const [width, setWidth] = useState(0);
  const divRef = useRef(null);

  useLayoutEffect(() => {
    console.log("useLayoutEffect: 在浏览器绘制之前运行。");
    setWidth(divRef.current.offsetWidth);
  });

  return (
    <div ref={divRef} style={{ width: `${width}px`, backgroundColor: "lightblue" }}>
      Width: {width}
    </div>
  );
};
export default Example;
  • 关键点: useLayoutEffect 中的代码在 DOM 更新后立即运行,因此可以在浏览器绘制前修改 DOM。

4. 性能影响

  • useEffect:

    • 非阻塞,不会影响页面的首次绘制,适合大多数场景。
    • 更推荐默认使用 useEffect,只有在明确需要操作 DOM 的情况下才考虑 useLayoutEffect
  • useLayoutEffect:

    • 阻塞绘制,可能会导致性能问题,尤其是在复杂的组件中。
    • 仅在需要精确操作 DOM 或测量布局时使用。

5. 常见问题

为什么不直接用 useLayoutEffect 替代 useEffect

  • useLayoutEffect 会阻塞浏览器绘制,可能导致性能问题,因此只有在确实需要操作 DOM 的情况下才使用。

服务端渲染(SSR)时有什么不同?

  • useLayoutEffect 在服务端渲染时会产生警告,因为它需要依赖 DOM 环境。
  • 推荐在服务端渲染中使用 useEffect

6. 总结

特性useEffectuseLayoutEffect
执行时机浏览器绘制后浏览器绘制前
是否阻塞绘制
适用场景数据获取、订阅、非布局相关的副作用精确测量 DOM 或操作布局相关的副作用
性能影响更高效,默认推荐使用可能影响性能,仅在必要时使用

在实际开发中,默认使用 useEffect,只有在需要操作或测量 DOM 时使用 useLayoutEffect