const ref = useRef() <InputNumber ref={ref} /> ref.current.focus()在这个案例里,我们的目标是直接获取到 input 对象,并且调用它的 focus 方法,让 input 获得焦点。
var input = document.getElementById('input') input.focus()但是在模块的划分上,input 元素本身并不属于当前模块/组件,因此,调用 input 方法的行为,其实是属于一种混乱。除非我们不做解耦和封装。因此,在 React 的组件封装中,并不支持直接获取到 input 的引用,而是以一种传入控制器的方式来调用它。在这个场景里:
import { forwardRef } from 'react'; function MyInput(props, ref) { // ... } const InputNumber = forwardRef(MyInput);这里需要注意的是,我们需要把 ref 放在自定义组件的参数中
function MyInput(props, ref) { // ... }forwardRef 返回一个可渲染的函数组件。因此,我们可以通过一个简单的案例完善上面的代码
function MyInput(props, ref) { return ( <label> {props.label} <input ref={ref} /> </label> ); } const InputNumber = forwardRef(MyInput)所以呢,在 React 的开发中,forwardRef 能够帮助我们实现更良好的解耦,这也是我一直非常喜欢使用 forwardRef 的原因。
function MyInput(props) { return ( <label> {props.label} <input ref={props.ref} /> </label> ); }其次,代码这样写了之后,就可以直接在父组件中,通过 ref 拿到 input 的控制权
function Index() { const input = useRef(null) function __clickHandler() { input.current.focus() } return ( <div> <button onClick={__clickHandler}> 点击获取焦点 </button> <MyInput ref={input} /> </div> ) }在父组件中的使用与以前一样,但是子组件由于不再需要 forwardRef,变得更加简单了。
3.deps: 依赖项数组,可选。state,props 以及内部定义的其他变量都可以作为依赖项,React 内部会使用 Object.is 来对比依赖项是否发生了变化。依赖项发生变化时,createHandle 会重新执行,ref 引用会更新。如果不传入依赖项,那么每次更新 createHandle 都会重新执行
<> <button>Write a comment</button> <Post /> </>我们期望点击按钮时,信息部分的输入框自动获取焦点,信息部分的信息展示区域能滚动到最底部,因此整个页面组件的代码可以表示为如下:
import { useRef } from 'react'; import Post from './Post.js'; export default function Page() { const postRef = useRef(null); function handleClick() { postRef.current.scrollAndFocusAddComment(); } return ( <> <button onClick={handleClick}> Write a comment </button> <Post ref={postRef} /> </> ); }再来思考信息部分。信息部分 Post 又分为两个部分,分别是信息展示部分与信息输入部分。此时这两个部分的 ref 要透传给 Post,并最终再次透传给页面组件。因此他们的组件结构应该长这样
<> <article> <p>Welcome to my blog!</p> </article> <CommentList ref={commentsRef} /> <AddComment ref={addCommentRef} /> </>这个时候,有一个需要考虑的点就是,有两个对象需要被控制,因此我们需要借助 useImperativeHandle 来自定义控制器,并在控制的方法中,整合他们
useImperativeHandle(ref, () => { return { scrollAndFocusAddComment() { commentsRef.current.scrollToBottom(); addCommentRef.current.focus(); } }; }, []);ref 的传递,只需要把它看成是一个普通的 props 即可,因此,Post 组件完整代码如下
const Post = ({ref}) => { const commentsRef = useRef(null); const addCommentRef = useRef(null); // 堆代码 duidaima.com useImperativeHandle(ref, () => { return { scrollAndFocusAddComment() { commentsRef.current.scrollToBottom(); addCommentRef.current.focus(); } }; }, []); return ( <> <article> <p>Welcome to my blog!</p> </article> <CommentList ref={commentsRef} /> <AddComment ref={addCommentRef} /> </> ); }同样的道理,我们只需要把 CommentList 与 AddComment 的 ref 传递出来就可以了。所以信息展示部分 CommentList 组件的代码为
import { useRef, useImperativeHandle } from 'react'; const CommentList = ({ref}) => { const divRef = useRef(null); useImperativeHandle(ref, () => { return { scrollToBottom() { const node = divRef.current; node.scrollTop = node.scrollHeight; } }; }, []); let comments = []; for (let i = 0; i < 50; i++) { comments.push(<p key={i}>Comment #{i}</p>); } return ( <div className="CommentList" ref={divRef}> {comments} </div> ); }; export default CommentList;信息输入部分 AddComment 的代码为
function AddComment(props) { return ( <input placeholder="Add comment..." ref={props.ref} /> ) }; export default AddComment;与之前相比,确实要简单了许多。