何时使用useImperativeHandle,useLayoutEffect和useDebugValue

时间:2019-07-12 11:02:27

标签: reactjs

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

4 个答案:

答案 0 :(得分:17)

允许我通过说所有这些挂钩很少使用来开头这个答案。 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()计算父项的身高和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"));
.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中显示自定义钩子的标签。

答案 1 :(得分:1)

useImperativeHandle

想象一下您创建了一个组件Table。您可能需要指定refs上的Table与该组件的交互方式;例如,您可能想提供一个特定的单元,而不是仅 能够访问根元素。

[index.tsx]

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

import "./styles.css";

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

  useEffect(() => {
    tableRef.current.style.color = "green";
  }, []);

  return (
    <div>
      <Table ref={tableRef} />
    </div>
  );
}

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

[Table.tsx]

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

function Table(props, ref) {
  const cellRef = useRef(null);
  useImperativeHandle(ref, () => cellRef.current);

  return (
    <table>
      <tr>
        <td>First</td>
        <td ref={cellRef}>Second</td>
      </tr>
    </table>
  );
}

export default forwardRef(Table);

Available here.

useLayoutEffect

这与useEffect相同,但是仅在所有DOM突变完成后才触发。 This article肯特·C·多德斯(Kent C. Dodds)以及其他任何人都解释了两者之间的区别,他说:

  

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

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

useDebugValue

我觉得the docs做了一个很好的例子来说明这一点。如果您有一个自定义钩子,并且想在React DevTools中标记它,那么您将使用它。

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

答案 2 :(得分:1)

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

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

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

这很容易通过在 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>
    );
});

答案 3 :(得分:0)

useImperativeHandle

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

`
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?(
       <aside className="sidebar">
          <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 

`

`

Class Main扩展了组件{

 myRef=createRef();

 render(){
  return(
   <Fragment>
     <button onClick={()=>{
                 ///hear we calling sidebar component
                 this.myRef?.current?.opensideBar()
                }}>
      Show Sidebar
    </button>
    <Sidebar ref={this.myRef}/>
   </Fragment>
  )
 }

}

`