[React] useEffect 首次渲染问题

324 阅读2分钟

useEffect 首次渲染问题

  • 在使用 useEffect 时候,会在页面渲染完成后执行一次
  • 可以使用 useRef,避免 useEffect 的第一次渲染
  • useRef 在组件的整个生命周期中持续存在
const renderRef = useRef(true);
​
useEffect(() => {
  // 阻止第一次渲染
  if (renderRef.current) {
    renderRef.current = false;
    return;
  }
  // todo something
}, [data]);

useEffect hook 学习

useEffect

  • 该 Hook 接收一个包含命令式、且可能有副作用代码的函数
  • 赋值给 useEffect 的函数会在组件渲染到屏幕之后执行
  • 默认情况下,Effect 将在每轮渲染结束后执行,但可以让它在某些值改变时候执行
useEffect(didUpdate);

什么是副作用代码?

  • 在函数组件主体内,这里指在 React 渲染阶段
  • 改变 DOM、添加订阅、设置定时器、记录日志及其他包含副作用的操作
  • 这可能产生莫名其妙的 bug 并破坏 UI 的一致性

清除 Effect

  • 通常,组件卸载时需要清除 Effect 创建的诸如订阅或定时器 ID 等资源
  • 要实现这一点,useEffect 函数需返回一个清除函数
  • 为防止内存泄漏,清除函数会在组件卸载前执行
  • 如果组件多次渲染(通常如此),在执行下一个 effect 之前,上一个 effect 就已被清除
useEffect(() => {
  const subscription = props.source.subscribe();
  return () => {
    // 清除订阅
    subscription.unsubscribe();
  };
});

Effect 的执行时机

  • 浏览器完成布局与绘制之后,传给 useEffect 的函数会延迟调用
  • 因此不应在函数中执行阻塞浏览器更新屏幕的操作
  • 并非所有 effect 都可以被延迟执行,例如在浏览器执行下一次绘制前,用户可见的 DOM 变更就必须同步执行,这样用户才不会感觉到视觉上的不一致
  • React 为此提供了一个额外的 useLayoutEffect Hook 来处理这类 effect
  • useLayoutEffect 与 useEffect 的结构相同,区别只是调用时机不同

useEffect 第二个参数

  • 是 effect 所依赖的值数组
  • 如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([]
useEffect(
  () => {
    const subscription = props.source.subscribe();
    return () => {
      subscription.unsubscribe();
    };
  },
  [props.source],
);

useRef hook 学习

  • 访问 DOM 的主要方式,返回的 ref 对象在组件的整个生命周期内持续存在
  • useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数
  • useRef() 和自建一个 {current: ...} 对象的唯一区别是,useRef 会在每次渲染时返回同一个 ref 对象
const refContainer = useRef(initialValue);
function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` 指向已挂载到 DOM 上的文本输入元素
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

useRef 的常见用途

  • 获取和操作DOM元素
  • 在动画中保存元素的位置信息
  • 与第三方库集成,例如Chart.js 或 D3.js
  • 保存上一个渲染周期的值,以进行比较
  • 跟踪焦点管理
  • 与 setInterval 或 setTimeout 等定时器一起使用