我不能理解为什么下面的 useImperativeHandle,useLayoutEffect,和 useDebugValue钩子是必要的,你能给出例子时,他们可以使用,但不从文件的例子请。
useImperativeHandle
useLayoutEffect
useDebugValue
useImperativeHandle允许您确定将在引用上公开哪些属性。在下面的示例中,我们有一个按钮组件,我们希望在这个 ref 上公开 someExposedProperty属性:
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);
在这里可以买到。
这与 useEffect相同,但只在所有 DOM 突变完成后才触发。来自肯特 C。多兹和其他人一样解释了这两者的区别,他说:
useEffect
99% 的时间[ useEffect]是你想要使用的。
我还没有看到任何例子能够很好地说明这一点,我也不确定我能够创造任何东西。可能最好的说法是,当 useEffect有问题时,您应该只使用 useLayoutEffect。
我觉得 那些文件做了一个很好的例子来解释这一点。如果您有一个自定义钩子,并且您想在 React DevTools 中标记它,那么这就是您所使用的。
如果你对此有任何具体的问题,那么最好要么评论,要么问另一个问题,因为我觉得人们在这里提出的任何东西都只是在重复文档,至少在我们达到一个更具体的问题之前。
请允许我在这个答案的前面说明,所有这些钩子都很少使用。99% 的情况下,你不需要这些。它们只能覆盖一些罕见的极端情况。
通常,当您使用 useRef时,您会得到 ref所附加到的组件的实例值。这允许您直接与 DOM 元素交互。
useRef
ref
useImperativeHandle非常类似,但它让你做两件事:
blur
focus
您可能有许多理由希望执行上述任何一种操作; 您可能不希望将本机属性公开给父级,或者可能希望更改本机函数的行为。可能有很多原因。但是,很少使用 useImperativeHandle。
useImperativeHandle自定义在使用 ref时公开给父组件的实例值
例子
在本例中,我们将从 ref获得的值将只包含我们在 useImperativeHandle中声明的函数 blur。它不包含任何其他属性(我正在记录这个值来演示这一点)。该函数本身也是“定制的”,其行为与您通常期望的不同。在这里,它设置 document.title并在调用 blur时模糊输入。
document.title
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>
虽然在某种程度上类似于 useEffect(),但它的不同之处在于,它将在 React 向 DOM 提交更新之后运行。在极少数情况下,需要在更新后计算元素之间的距离或进行其他更新后计算/副作用时使用。
useEffect()
该签名与 useEffect相同,但是在所有 DOM 突变之后同步激发。使用此选项可以从 DOM 读取布局并同步重新呈现。在 useLayoutEffect中安排的更新将同步刷新,即 在浏览器有机会绘制之前。
假设您有一个绝对定位的元素,它的高度可能会发生变化,并且您希望在它下面定位另一个 div。您可以使用 getBoundingClientRect()来计算父类的 height 和 top 属性,然后将它们应用到子类的 top 属性。
div
getBoundingClientRect()
在这里你可能想使用 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; }
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"));
有时您可能希望调试某些值或属性,但是这样做可能需要开销很大的操作,这可能会影响性能。
只有当 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 使用布局效果钩子的区别: 反应效果初学者指南