请允许我在这个答案的前言中指出,所有这些钩子都很少使用。 99% 的情况下,您不需要这些。它们只是为了涵盖一些罕见的极端情况。
useImperativeHandle
通常当你使用useRef
您将获得组件的实例值ref
附于.这允许您直接与 DOM 元素交互。
useImperativeHandle
非常相似,但它可以让你做两件事:
- 它使您可以控制返回的值。您不返回实例元素,而是明确声明返回值是什么(请参见下面的代码片段)。
- 它允许您替换本机函数(例如
blur
, focus
等)具有您自己的功能,从而允许对正常行为产生副作用,或者完全不同的行为。不过,您可以随意调用该函数。
您想要执行上述任一操作的原因可能有很多;您可能不想向父级公开本机属性,或者您可能想更改本机函数的行为。原因可能有很多。然而,useImperativeHandle
很少使用。
useImperativeHandle
自定义使用时暴露给父组件的实例值ref
Example
在此示例中,我们将从中获得的值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
将被同步刷新,在浏览器有机会绘制之前.
Example
假设您有一个绝对定位的元素,其高度可能会有所不同,并且您想要定位另一个元素div
在它下面。你可以使用getBoundingClientRect()
计算父级的高度和顶部属性,然后将它们应用到子级的顶部属性。
在这里你想使用useLayoutEffect
而不是useEffect
。请参阅下面的示例了解原因:
With 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>
With 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 中显示自定义挂钩的标签。
但我个人从未使用过这个钩子。也许评论中的人可以通过一个很好的例子来提供一些见解。