reactjs - 何时使用 useImperativeHandle、useLayoutEffect 和 useDebugValue

标签 reactjs react-native

我不明白为什么需要以下 useImperativeHandleuseLayoutEffectuseDebugValue 钩子(Hook),您能否举例说明何时可以使用它们,但请不要使用文档中的示例。

最佳答案

请允许我在这个答案的前言中指出,所有这些钩子(Hook)都很少使用。 99% 的情况下,您不需要这些。它们只是为了涵盖一些罕见的极端情况。

<小时/>

useImperativeHandle

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

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

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

您可能出于多种原因想要执行上述任一操作;您可能不想向父级公开 native 属性,或者您可能想更改 native 函数的行为。原因可能有很多。但是,useImperativeHandle很少使用。

useImperativeHandle customizes the instance value that is exposed to parent components when using ref

示例

在此示例中,我们从 ref 获取的值将仅包含我们在 useImperativeHandle 中声明的函数 blur。它不会包含任何其他属性(我正在记录该值来演示这一点)。该函数本身也经过“定制”,其行为与您通常期望的不同。在这里,它设置 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 提交更新后运行。在极少数情况下使用,当您需要在更新后计算元素之间的距离或进行其他更新后计算/副作用时。

The signature is identical to useEffect, but it fires synchronously after all DOM mutations. Use this to read layout from the DOM and synchronously re-render. Updates scheduled inside useLayoutEffect will be flushed synchronously, before the browser has a chance to paint.

示例

假设您有一个绝对定位的元素,其高度可能会有所不同,并且您想在其下方放置另一个 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 打开并检查相关钩子(Hook)时调用,防止对性能产生任何影响。

useDebugValue can be used to display a label for custom hooks in React DevTools.

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

关于reactjs - 何时使用 useImperativeHandle、useLayoutEffect 和 useDebugValue,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57005663/

相关文章:

node.js - 如何使用reactjs将导入的csv数据显示到表格中?

jquery - react 错误 : __WEBPACK_IMPORTED_MODULE_4_jquery___default(. ..)(...).modal 不是函数

mysql - React Native 获取 MYSQL 中的数据

javascript - 为什么我的 CSS 不工作

reactjs - 为什么 React 应用程序在布局渲染阶段表现不佳?

javascript - react : e. 目标。?日志未定义

android - React Native 模块 (Android) - TypeError : undefined is not an object

javascript - React Navigation 嵌套导航器中奇怪的后退按钮行为

android - 在 React Native 中使用 Expo 会最差吗?

react-native - 在 react-navigation 中使用 react-redux connect 方法显示错误 "The component for route ' x' must be a React component