何时使用useImperativeHandle,useLayoutEffect和useDebugValue

10 reactjs

我不明白为什么需要以下useImperativeHandle,useLayoutEffect和useDebugValue挂钩,您可以在可以使用它们的情况下提供示例,但请从文档中获取示例。

Chr*_*ris 17

请允许我通过说所有这些钩子很少使用来对这个答案做一个开头。99%的时间,您将不需要这些。它们仅旨在涵盖一些罕见的极端情况。


useImperativeHandle

通常,在使用时会为useRef您提供ref附加到该组件的实例值。这使您可以直接与DOM元素进行交互。

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

  1. 它使您可以控制返回的值。您无需显式声明返回值,而无需返回instance元素(请参见下面的代码段)。
  2. 它可以让你更换本地函数(如blurfocus用自己的功能,等等),从而使副作用的正常行为,或者不同的行为完全。虽然,您可以随意调用该函数。

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

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

在此示例中,我们将从中获得的值ref将仅包含在中blur声明的函数useImperativeHandle。它不会包含任何其他属性(我正在记录该值来演示此内容)。该函数本身也被“定制”为与通常期望的行为不同。在这里,它会document.titleblur调用时设置和模糊输入。

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"));
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)


useLayoutEffect

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

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

假设您有一个绝对定位的元素,其高度可能会发生变化,并且您想div在其下方放置另一个元素。您可以getBoundingCLientRect()用来计算父项的高度和top属性,然后将其应用于子项的top属性。

在这里您要使用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"));
Run Code Online (Sandbox Code Playgroud)
.box {
  position: absolute;
  width: 100px;
  height: 100px;
  background: green;
  color: white;
}

.msg {
  position: relative;
  border: 1px solid red;
}
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)

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"));
Run Code Online (Sandbox Code Playgroud)
.box {
  position: absolute;
  width: 100px;
  height: 100px;
  background: green;
  color: white;
}

.msg {
  position: relative;
  border: 1px solid red;
}
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)


useDebugValue

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

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

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


Yam*_*o93 13

这个useImperativeHandle钩子对我的一个用例帮助很大。

我创建了一个使用第三方库组件的网格组件。该库本身有一个大数据层,具有内置功能,可以通过访问元素的实例来使用。

但是,在我自己的网格组件中,我想使用在网格上执行操作的方法来扩展它。此外,我还希望能够从网格组件外部执行这些方法。

通过在钩子内部添加方法可以轻松实现这一点useImperativeHandle,然后它们将被其父级公开并可用。

我的网格组件看起来有点像这样:

import React, { forwardRef, useImperativeHandle, useRef } from 'react';
import ThirdPartyGrid from 'some-library';

export default forwardRef((props, forwardedRef) => {
    const gridRef = useRef(null);
    useImperativeHandle(forwardedRef,
        () => ({

            storeExpandedRecords () {
                // Some code
            },

            restoreExpandedRecords () {
                // Some code
            },

        }));
    
    return (
        <div ref={forwardedRef}>
            <ThirdPartyGrid 
                ref={gridRef}
                {...props.config}
            />
        </div>
    );
});

Run Code Online (Sandbox Code Playgroud)

然后在我的父级中,我可以执行这些方法:

import React, { useRef, useEffect } from 'react';

export default function Parent () {
    const gridRef = useRef(null);

    const storeRecords = () => {
        gridRef.current.storeExpandedRecords();
    };

    useEffect(() => {
        storeRecords();
    }, []);

    return <GridWrapper ref={gridRef} config={{ something: true }} />
};
Run Code Online (Sandbox Code Playgroud)

  • 我在一个非常相似的用例项目上使用了这个 - 完整示例在这里 https://github.com/johndpope/grid/blob/main/src/SupabaseGrid.tsx (2认同)

Oli*_*ini 10

useImperativeHandle

useImperativeHandle允许您确定将在 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);
Run Code Online (Sandbox Code Playgroud)

[按钮.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);
Run Code Online (Sandbox Code Playgroud)

在这里可用。

useLayoutEffect

这与 相同useEffect,但只有在所有 DOM 更改完成后才会触发。这篇来自 Kent C. Dodds 的文章解释了两者之间的差异,他说:

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

我还没有看到任何能很好地说明这一点的例子,我也不确定我是否能够创造出任何东西。最好说你应该只useLayoutEffectuseEffect有问题时使用。

useDebugValue

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

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

  • 您的“useImperativeHandle”示例不起作用。 (2认同)
  • `useImperativeHandle` 示例只是在 `Table.jsx` 中缺少正确的代码,我刚刚复制了您提供的代码片段并且它可以工作。谢谢! (2认同)