关于reactjs:何时使用useImperativeHandle,useLayoutEffect和useDebugValue

When to use useImperativeHandle, useLayoutEffect, and useDebugValue

我不明白为什么需要以下useImperativeHandleuseLayoutEffectuseDebugValue钩子,您能否在可以使用的情况下提供示例,但请从文档中获取示例。


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

useImperativeHandle

通常,当您使用useRef时,会得到ref所连接组件的实例值。这使您可以直接与DOM元素进行交互。

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

  • 它使您可以控制返回的值。您无需返回实例元素,而是显式声明返回值(请参见下面的代码段)。
  • 它允许您用自己的函数替换本机函数(例如blurfocus等),从而使正常行为或完全不同的行为产生副作用。不过,您可以随心所欲地调用该函数。
  • 您可能要执行上述任何一项操作,可能有很多原因;您可能不想将本机属性公开给父级,或者您想更改本机函数的行为。可能有很多原因。但是,很少使用useImperativeHandle

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

    示例

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    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"));

    1
    2
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js">

    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()计算父项的高度和top属性,然后将其应用于子项的top属性。

    在这里您要使用useLayoutEffect而不是useEffect。在以下示例中查看原因:

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    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 (
       
           setShow(prev => !prev)}>Click me
          {show && <Message boxRef={boxRef}>Foo bar baz</Message>}
       
      );
    };

    ReactDOM.render(<App />, document.getElementById("app"));
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    .box {
      position: absolute;
      width: 100px;
      height: 100px;
      background: green;
      color: white;
    }

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

    使用useLayoutEffect

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    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 (
       
           setShow(prev => !prev)}>Click me
          {show && <Message boxRef={boxRef}>Foo bar baz</Message>}
       
      );
    };

    ReactDOM.render(<App />, document.getElementById("app"));
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    .box {
      position: absolute;
      width: 100px;
      height: 100px;
      background: green;
      color: white;
    }

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

    useDebugValue

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

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

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

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


    useImperativeHandle

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

    [index.tsx]

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    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 (
       
          <Button onClick={handleClick} ref={buttonRef} />
       
      );
    }

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

    [Button.tsx]

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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% of the time [useEffect] is what you want to use.

    我还没有看到任何示例可以很好地说明这一点,而且我不确定我也可以创建任何东西。最好说当useEffect出现问题时,您应该只使用useLayoutEffect
    useDebugValue

    我觉得文档是一个很好的例子来说明这一点。如果您有一个自定义钩子,并且希望在React DevTools中对其进行标记,那么您将使用它。

    如果您对此有任何特定的问题,那么最好是发表评论或提出其他问题,因为我觉得人们在这里所做的任何事情都只会重申文档,至少直到我们遇到一个更具体的问题为止。


    useImperativeHandle

    通常通过将功能组件置于forwardRef内,从而将基于功能的组件方法和属性公开给其他组件
    例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    const Sidebar=forwardRef((props,ref)=>{

           const [visibility,setVisibility]=useState(null)
           
           const opensideBar=()=>{
             setVisibility(!visibility)
           }
            useImperativeHandle(ref,()=>({
               
                opensideBar:()=>{
                   set()
                }
            }))
           
           return(
            <Fragment>
             <button onClick={opensideBar}>SHOW_SIDEBAR</button>
             {visibility==true?(
               
                  <ul className="list-group">
                    <li className=" list-group-item">HOME
    </li>

                    <li className=" list-group-item">ABOUT
    </li>

                    <li className=" list-group-item">SERVICES
    </li>

                    <li className=" list-group-item">CONTACT
    </li>

                    <li className=" list-group-item">GALLERY
    </li>

                 
    </ul>

              </aside>
             ):null}
            </Fragment>
           )
         }
       
       
        //using sidebar component
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class Main extends Component{
       
         myRef=createRef();
       
         render(){
          return(
           <Fragment>
             <button onClick={()=>{
                         ///hear we calling sidebar component
                         this.myRef?.current?.opensideBar()
                        }}>
              Show Sidebar
            </button>
            <Sidebar ref={this.myRef}/>
           </Fragment>
          )
         }
     }