react hook useLayoutEffect、useImperativeHandle

214 阅读4分钟

一、useLayoutEffect

useEffect 类似,但它会在 DOM 更新后同步执行,这对于需要与布局相关的操作很有用。 而 useEffect 的回调函数则是在屏幕绘制之后异步执行。

使用场景

  • useEffect:适用于那些不需要与 DOM 布局同步的操作,如数据获取、订阅事件、执行动画等。
  • useLayoutEffect:适用于那些需要在布局更新后立即执行的操作,如获取元素尺寸、调整布局等。

注意事项

由于其同步性质,useLayoutEffect 可能会导致渲染性能问题,尤其是在执行耗时操作时。如果在 useLayoutEffect 中执行的操作很复杂或需要大量计算,它可能会阻塞主线程,影响应用的流畅度。

总结:

useEffectuseLayoutEffect 都可以用来处理副作用,但 useEffect 是异步执行的,适合大多数副作用场景,而 useLayoutEffect 是同步执行的,适合那些需要在布局更新后立即进行的操作。

二、useImperativeHandle

useImperativeHandle 是 React 中的一个 Hook,它允许你在使用 ref 时自定义暴露给父组件的实例值或方法。这在创建可重用的组件时特别有用,尤其是当你需要确保子组件的实例可以被父组件直接操控时。

实际使用例子

父组件control按钮能设置子组件宽高,同时将focus子组件的input。

image.png
父组件:

const resizableBoxRef = useRef();
const handleResize = (newWidth: any, newHeight: any) => {
  resizableBoxRef.current.setSize(newWidth, newHeight);
  resizableBoxRef.current.focus();
};
return(
    <div>
       <ResizableBox ref={resizableBoxRef} />
       <Button onClick={() => handleResize(200, 200)}>control</Button>
    </div>
)

子组件:

// 当你需要将 `ref` 从外部传递到内部组件时,`forwardRef` 能够确保 `ref` 能够正确地被传递和挂载。
import { useImperativeHandle, forwardRef, useRef, useState } from 'react';
export default forwardRef(function ResizableBox(props, ref) {
  // 保存当前的大小
  const [size, setSize] = useState({ width: 100, height: 100 });
  const inputRef = useRef(null);
  // 处理大小变化的函数
  const handleResize = (newWidth: any, newHeight: any) => {
    setSize({ width: newWidth, height: newHeight });
  };
  useImperativeHandle(ref, () => ({
    setSize: (newWidth, newHeight) => {
      handleResize(newWidth, newHeight);
    },
    // 暴露获取当前大小的方法
    getSize: () => size,
    focus() {
      inputRef.current.focus();
    }
  }));

  return (
    <div style={{ width: `${size.width}px`, height: `${size.height}px`, background: 'yellow' }}>
      <input type="text" className="input-text-xs" ref={inputRef} placeholder="入力してください" />
    </div>
  );
});

在这个例子中,子组件使用 useState 来管理自己的【宽/高】状态。通过 useImperativeHandle,我们将 setSizefocus方法暴露给了父组件。 这样,父组件就可以直接控制子组件的行为,而不需要通过组件的公共 API 或者状态提升来实现。这在创建可重用组件和封装复杂逻辑时非常有用。

使用场景

1. 控制子组件的方法和属性: 当你创建了一个复杂的子组件,它封装了一些功能和状态,但你仍然希望父组件能够直接调用一些方法或访问一些属性时,useImperativeHandle 就非常有用。通过 useImperativeHandle,你可以暴露一个或多个方法或属性给父组件,而不需要将它们作为组件的公共 API。

2.管理复杂的交互: 当你需要管理一些复杂的用户交互或动画时,useImperativeHandle 可以让你在父组件中控制子组件的行为。例如,你可以在父组件中控制动画的播放、暂停或停止,而动画逻辑则封装在子组件中。

3.自定义组件的生命周期:在使用 useImperativeHandle 时,你可以更好地控制子组件的生命周期,例如,你可以在子组件被挂载或更新时执行特定的操作,而不是在每次父组件渲染时都执行这些操作。

4.优化 DOM 操作:在使用 useImperativeHandle 时,你可以将 DOM 操作封装在子组件内部,而不是在父组件中直接操作 DOM。这样可以减少父组件的负担,让子组件管理自己的 DOM 状态,从而提高整体性能。

5.避免不必要的渲染: 在某些情况下,通过 useImperativeHandle 暴露的方法可以直接操作子组件的内部状态,你可以确保父组件只调用那些真正需要更新子组件状态的方法。这样,你可以避免由于父组件的频繁操作而导致的子组件的不必要重新渲染,这有助于优化性能。