Poznajemy przykład dość zaawansowanego używania useRef, forwardRef oraz useImperativeHandle. Upewniamy się, że wszystko rozumiemy. Do dzieła.
Ok, będziemy mieli:
- Komponent najniższego rzędu, forwardujący ref od rodzica do HTMLElement
- Rodzeństwo najniższego rzędu, również forwardujące ref od rodzica, ale nie bezpośrednio do swojego elementu, tylko do useImperativeHandle
- Komponent pośredni, który:
- Przyjmuje ref od rodzica
- Forwarduje ten ref do useImperativeHandle
- Sam tworzy dwa refy, które wskazują nie na HTMLElement, ale jego dzieci RFC
- Używa tych dwóch refów wewnątrz useImperativeHandle
- Komponent-rodzic, który tworzy jakiś ref i przekazuje go do komponentu pośredniego
Ok, jak to wygląda. Pierwszy z komponentów najniżej:
const AddComment = forwardRef(function AddComment(props, ref) {
return <input placeholder="Add comment..." ref={ref} />;
});
export default AddComment;
Jak widać coś przyjmuje, jakiś ref utworzony wyżej i wskazujący poprzez atrybut ref na RFC AddComponent. Przyjmuje i forwarduje bezpośrednio do swojego HMTLInputElement.
Teraz jego rodzeństwo:
const CommentList = forwardRef(function CommentList(props, 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;
Jak widać ten komponent również jest najniższego rzędu, ponieważ ref wskazuje na HTMLDivElement a nie jakiś komponent RFC. Ale wskazuje jego własny divRef, zaś ten przyjmowany od swojego rodzica, komponentu pośredniego, jest używany do useImperativeHandle.
Tam, w useImperativeHandle dokonuje pewnych akcji swoim prywatnym refem na HTMLDivElement pokazującym i zwraca publicznie tylko jedną metodę. Komponent pośredni, komponent rodzic będzie mógł mu zrobić ref, ale ten ref będzie mógł tylko metodę scrollToBottm na jego divie wykonać.
Ok, komponent pośredni:
import { forwardRef, useRef, useImperativeHandle } from 'react';
import CommentList from './CommentList.js';
import AddComment from './AddComment.js';
const Post = forwardRef((props, ref) => {
const commentsRef = useRef(null);
const addCommentRef = useRef(null);
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} />
</>
);
});
export default Post;
Ten komponent:
- Jest komponentem pośrednim, ponieważ ma refy wskazujące na RFC, które forwardują te refy dalej (do HTMLElementu, albo useImperativeHandle, które pracuje na HTMLElemencie swoim prywatnym refem)
- Przyjmuje ref od rodzica i używa w useImperativeHandle, rodzic może zrobić mu refa, ale będzie mógł korzystać tylko z metod, które on mu zapewnia
- Tworzy dwa refy, które są do komponentów RFC, które je forwardują niżej, jeden komponent bezpośrednio do HTMLElement, drugi pośrednio do useImperativeHandle, które tylko metodę scrollToBottom publicznie udostępnia
- Wewnątrz własnego useImperativeHandle, komponent pośredni korzysta ze swoich refów i tego, co mu te komponenty niżej forwardują, rodzic komponentu pośredniego będzie mógł mu ustawić ref i tylko te metody wywołać
Ok, oto rodzic:
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} />
</>
);
}
Przeanalizujmy sobie, jak to wszystko działa, bo nie jest to trudne, ale trzeba to dobrze zrozumieć.