何时使用 useImperativeHandle、useLayoutEffect 和 useDebugValue

新手上路,请多包涵

我不明白为什么需要以下 useImperativeHandleuseLayoutEffectuseDebugValue 钩子,你能给出可以使用的例子,而不是文档中的例子请。

原文由 user11749073 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.2k
2 个回答

请允许我通过说明所有这些钩子都很少使用来作为这个答案的序言。 99% 的时间,你不需要这些。它们仅用于涵盖一些罕见的极端情况。


useImperativeHandle

通常,当您使用 useRef 时,您会获得 ref 附加到的组件的实例值。这允许您直接与 DOM 元素交互。

useImperativeHandle 非常相似,但它可以让你做两件事:

  1. 它使您可以控制返回的值。您无需返回实例元素,而是明确说明返回值将是什么(请参见下面的代码段)。
  2. 它允许您用您自己的功能替换本机功能(例如 blurfocus 等),从而允许对正常行为或完全不同的行为产生副作用。不过,您可以随意调用该函数。

您可能有很多原因可能会执行上述任一操作;您可能不想将本机属性公开给父级,或者您可能想更改本机函数的行为。可能有很多原因。但是, useImperativeHandle 很少使用。

useImperativeHandle 自定义使用时暴露给父组件的实例值 ref

例子

在此示例中,我们将从 ref 获得的值将仅包含我们在 blur useImperativeHandle 。它不会包含任何其他属性( _我正在记录值以证明这一点_)。该函数本身也是“定制的”,其行为与您通常期望的不同。在这里,它设置 document.title 并在调用 blur 时模糊输入。

 const MyInput = React.forwardRef((props, ref) => {
  const [val, setVal] = React.useState('');
  const inputRef = React.useRef();

  React.useImperativeHandle(ref, () => ({
    blur: () => {
      document.title = val;
      inputRef.current.blur();
    }
  }));

  return (
    <input
      ref={inputRef}
      val={val}
      onChange={e => setVal(e.target.value)}
      {...props}
    />
  );
});

const App = () => {
  const ref = React.useRef(null);
  const onBlur = () => {
    console.log(ref.current); // Only contains one property!
    ref.current.blur();
  };

  return <MyInput ref={ref} onBlur={onBlur} />;
};

ReactDOM.render(<App />, document.getElementById("app"));
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>

useLayoutEffect

虽然在某种程度上类似于 useEffect() ,但它的不同之处在于它将在 React 向 DOM 提交更新后运行。当您需要在更新后计算元素之间的距离或进行其他更新后计算/副作用时,在极少数情况下使用。

签名与 useEffect 相同,但它会在所有 DOM 突变后同步触发。使用它从 DOM 中读取布局并同步重新渲染。在 useLayoutEffect 中安排的更新将 在浏览器有机会绘制之前 同步刷新。

例子

假设您有一个绝对定位的元素,其高度可能会有所不同,并且您想在其下方放置另一个 div 。您可以使用 getBoundingCLientRect() 计算父级的高度和顶部属性,然后将它们应用于子级的顶部属性。

在这里你会想使用 useLayoutEffect 而不是 useEffect 。请参阅以下示例中的原因:

使用 useEffect :(注意跳跃行为)

 const Message = ({boxRef, children}) => {
  const msgRef = React.useRef(null);
  React.useEffect(() => {
    const rect = boxRef.current.getBoundingClientRect();
    msgRef.current.style.top = `${rect.height + rect.top}px`;
  }, []);

  return <span ref={msgRef} className="msg">{children}</span>;
};

const App = () => {
  const [show, setShow] = React.useState(false);
  const boxRef = React.useRef(null);

  return (
    <div>
      <div ref={boxRef} className="box" onClick={() => setShow(prev => !prev)}>Click me</div>
      {show && <Message boxRef={boxRef}>Foo bar baz</Message>}
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById("app"));
 .box {
  position: absolute;
  width: 100px;
  height: 100px;
  background: green;
  color: white;
}

.msg {
  position: relative;
  border: 1px solid red;
}
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>

使用 useLayoutEffect

 const Message = ({boxRef, children}) => {
  const msgRef = React.useRef(null);
  React.useLayoutEffect(() => {
    const rect = boxRef.current.getBoundingClientRect();
    msgRef.current.style.top = `${rect.height + rect.top}px`;
  }, []);

  return <span ref={msgRef} className="msg">{children}</span>;
};

const App = () => {
  const [show, setShow] = React.useState(false);
  const boxRef = React.useRef(null);

  return (
    <div>
      <div ref={boxRef} className="box" onClick={() => setShow(prev => !prev)}>Click me</div>
      {show && <Message boxRef={boxRef}>Foo bar baz</Message>}
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById("app"));
 .box {
  position: absolute;
  width: 100px;
  height: 100px;
  background: green;
  color: white;
}

.msg {
  position: relative;
  border: 1px solid red;
}
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>

useDebugValue

有时您可能想要调试某些值或属性,但这样做可能需要昂贵的操作,这可能会影响性能。

useDebugValue 仅在 React DevTools 打开并检查相关钩子时调用,以防止对性能产生任何影响。

useDebugValue 可用于在 React DevTools 中显示自定义挂钩的标签。

不过,我个人从未使用过这个钩子。也许评论中的某人可以通过一个很好的例子提供一些见解。

原文由 Chris 发布,翻译遵循 CC BY-SA 4.0 许可协议

useImperativeHandle

useImperativeHandle 允许您确定将在 ref 上公开哪些属性。在下面的示例中,我们有一个按钮组件,我们想在该 ref 上公开 someExposedProperty 属性:

[索引.tsx]

 import React, { useRef } from "react";
import { render } from "react-dom";
import Button from "./Button";

import "./styles.css";

function App() {
  const buttonRef = useRef(null);

  const handleClick = () => {
    console.log(Object.keys(buttonRef.current)); // ['someExposedProperty']
    console.log("click in index.tsx");
    buttonRef.current.someExposedProperty();
  };

  return (
    <div>
      <Button onClick={handleClick} ref={buttonRef} />
    </div>
  );
}

const rootElement = document.getElementById("root");
render(<App />, rootElement);

[按钮.tsx]

 import React, { useRef, useImperativeHandle, forwardRef } from "react";

function Button(props, ref) {
  const buttonRef = useRef();
  useImperativeHandle(ref, () => ({
    someExposedProperty: () => {
      console.log(`we're inside the exposed property function!`);
    }
  }));
  return (
    <button ref={buttonRef} {...props}>
      Button
    </button>
  );
}

export default forwardRef(Button);

在这里可用。

useLayoutEffect

这与 useEffect 相同,但只有在所有 DOM 变更完成后才会触发。 这篇 来自 Kent C. Dodds 的文章解释了关于这两者的差异以及任何人,他说:

99% 的时间 [ useEffect ] 是你想要使用的。

我还没有看到任何例子可以特别好地说明这一点,而且我也不确定我是否能够创造任何东西。最好的说法是,当 useEffect 出现问题时,您应该只使用 useLayoutEffect

useDebugValue

我觉得 文档 做了一个很好的例子来解释这个。如果你有一个自定义钩子,并且你想在 React DevTools 中标记它,那么这就是你使用的。

如果您对此有任何具体问题,那么最好发表评论或提出另一个问题,因为我觉得人们放在这里的任何内容都只是在重申文档,至少在我们遇到更具体的问题之前是这样。

原文由 OliverRadini 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题