何时使用 useImperativeHandle、 useLayouteffect 和 useDebugValue

我不能理解为什么下面的 useImperativeHandleuseLayoutEffect,和 useDebugValue钩子是必要的,你能给出例子时,他们可以使用,但不从文件的例子请。

106280 次浏览

useImperativeHandle

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

[ index.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);

[ Button.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 突变完成后才触发。来自肯特 C。多兹和其他人一样解释了这两者的区别,他说:

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

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

useDebugValue

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

如果你对此有任何具体的问题,那么最好要么评论,要么问另一个问题,因为我觉得人们在这里提出的任何东西都只是在重复文档,至少在我们达到一个更具体的问题之前。

请允许我在这个答案的前面说明,所有这些钩子都很少使用。99% 的情况下,你不需要这些。它们只能覆盖一些罕见的极端情况。


useImperativeHandle

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

useImperativeHandle非常类似,但它让你做两件事:

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

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

useImperativeHandle自定义在使用 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 提交更新之后运行。在极少数情况下,需要在更新后计算元素之间的距离或进行其他更新后计算/副作用时使用。

该签名与 useEffect相同,但是在所有 DOM 突变之后同步激发。使用此选项可以从 DOM 读取布局并同步重新呈现。在 useLayoutEffect中安排的更新将同步刷新,即 在浏览器有机会绘制之前

例子

假设您有一个绝对定位的元素,它的高度可能会发生变化,并且您希望在它下面定位另一个 div。您可以使用 getBoundingClientRect()来计算父类的 height 和 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

有时您可能希望调试某些值或属性,但是这样做可能需要开销很大的操作,这可能会影响性能。

只有当 React DevTools 打开并检查相关钩子时,才会调用 useDebugValue,以防止对性能造成任何影响。

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

不过我个人从来没用过这个钩子。也许有人可以在评论中提供一些洞察力与一个很好的例子。

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


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

我看到这个问题已经有了答案。因此,我只想分享我的关于 useImperativeHandle、 useEffect 和 useLayouteffect 的文章。

我已经写了一篇基于实例的文章 关于 useImperativeHandle钩子,你可以在这里查看: 通过示例使用 ImperativeHandle

我还写了一篇关于 反应的影响的文章,详细解释了 使用效果钩子和 UseLayouteffect 使用布局效果钩子的区别: 反应效果初学者指南