何时使用 useImperativeHandle、useLayoutEffect 和 useDebugValue

IT技术 reactjs react-native
2021-03-31 04:41:49

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

4个回答

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


useImperativeHandle

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

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

  1. 它使您可以控制返回的值。不是返回实例元素,而是明确说明返回值是什么(参见下面的片段)。
  2. 它可以让你更换本地函数(如blurfocus用自己的功能,等等),从而使副作用的正常行为,或者不同的行为完全。不过,您可以随意调用该函数。

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

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

例子

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


useLayoutEffect

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

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

例子

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

在这里你会想要使用useLayoutEffect而不是useEffect. 在下面的示例中了解原因:

useEffect:(注意跳跃的行为)

useLayoutEffect


useDebugValue

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

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

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

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

只是一个问题,如果我设计一个可重用的组件,它包含一些子组件,我想公开一个可以与子组件交互的 API:例如,网格组件中的搜索功能并突出显示搜索结果。使用 useImperativeHandle 是一个很好的理由吗?
2021-05-26 04:41:49
@user192344 没有足够的上下文让我回答。你需要什么并不完全清楚。我建议您发布一个新问题并解释您的问题:)
2021-06-18 04:41:49
@user192344 我认为您所描述的更像是 Redux 类型的作业,您可以在其中传递从 useReducer( ) 返回的调度函数。当你想公开一组函数来操作状态(把它想象成一个状态 API),虽然 useImperativeHandle() 可以工作,但这样使用它可能不是一个好习惯。
2021-06-21 04:41:49

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);

[按钮.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] 是您想要使用的。

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

useDebugValue

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

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

你的例子useImperativeHandle不起作用。
2021-06-03 04:41:49
useLayoutEffect 当你需要改变(改变)由 React 生成的 DOM 时,这对于将改变 DOM 的库与 React 集成很有用。
2021-06-03 04:41:49
useImperativeHandle示例只是在 中缺少正确的代码Table.jsx,我只是复制了您提供的代码段并且可以正常工作。谢谢!
2021-06-10 04:41:49
这是命令式句柄的一个非常糟糕的例子,因为你可以直接将 ref 传递给组件,而没有任何改变
2021-06-16 04:41:49
@sawtaytoes 当我在代码沙箱上构建它时它确实有效,在任何情况下,接受的答案似乎都比这个好得多
2021-06-20 04:41:49

这个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>
    );
});

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

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 }} />
};

使用命令句柄

通常通过将功能组件放在 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 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>
      )
     }
 }