useImperativeHandle

useImperativeHandle์€ ref๋กœ ๋…ธ์ถœ๋˜๋Š” ํ•ธ๋“ค์„ ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ์ •์˜ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” React ํ›…์ž…๋‹ˆ๋‹ค.

useImperativeHandle(ref, createHandle, dependencies?)

๋ ˆํผ๋Ÿฐ์Šค

useImperativeHandle(ref, createHandle, dependencies?)

์ปดํฌ๋„ŒํŠธ์˜ ์ตœ์ƒ์œ„ ๋ ˆ๋ฒจ์—์„œ useImperativeHandle ์„ ํ˜ธ์ถœํ•˜์—ฌ ๋…ธ์ถœํ•  ref ํ•ธ๋“ค์„ ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import { forwardRef, useImperativeHandle } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
useImperativeHandle(ref, () => {
return {
// ... ๋ฉ”์„œ๋“œ๋ฅผ ์—ฌ๊ธฐ์— ์ž…๋ ฅํ•˜์„ธ์š” ...
};
}, []);
// ...

์•„๋ž˜์—์„œ ๋” ๋งŽ์€ ์˜ˆ์‹œ๋ฅผ ํ™•์ธํ•˜์„ธ์š”.

๋งค๊ฐœ๋ณ€์ˆ˜

  • ref: forwardRef ๋ Œ๋” ํ•จ์ˆ˜์—์„œ ๋‘ ๋ฒˆ์งธ ์ธ์ž๋กœ ๋ฐ›์€ ref์ž…๋‹ˆ๋‹ค.

  • createHandle: ์ธ์ž๊ฐ€ ์—†๊ณ  ๋…ธ์ถœํ•˜๋ ค๋Š” ref ํ•ธ๋“ค์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. ํ•ด๋‹น ref ํ•ธ๋“ค์€ ์–ด๋– ํ•œ ์œ ํ˜•์ด๋“  ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ ๋…ธ์ถœํ•˜๋ ค๋Š” ๋ฉ”์„œ๋“œ๊ฐ€ ์žˆ๋Š” ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

  • (์„ ํƒ์ ) dependencies: createHandle ์ฝ”๋“œ ๋‚ด์—์„œ ์ฐธ์กฐํ•˜๋Š” ๋ชจ๋“  ๋ฐ˜์‘ํ˜• ๊ฐ’์„ ๋‚˜์—ดํ•œ ๋ชฉ๋ก์ž…๋‹ˆ๋‹ค. ๋ฐ˜์‘ํ˜• ๊ฐ’์€ props, state ๋ฐ ์ปดํฌ๋„ŒํŠธ ๋‚ด์—์„œ ์ง์ ‘ ์„ ์–ธํ•œ ๋ชจ๋“  ๋ณ€์ˆ˜์™€ ํ•จ์ˆ˜๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. React์— ๋Œ€ํ•œ ๋ฆฐํ„ฐ๋ฅผ ๊ตฌ์„ฑํ•œ ๊ฒฝ์šฐ ๋ชจ๋“  ๋ฐ˜์‘ํ˜• ๊ฐ’์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์˜์กด์„ฑ์œผ๋กœ ์ง€์ •๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์˜์กด์„ฑ ๋ชฉ๋ก์€ ํ•ญ์ƒ ์ผ์ •ํ•œ ์ˆ˜์˜ ํ•ญ๋ชฉ์„ ๊ฐ€์ง€๊ณ  [dep1, dep2, dep3]์™€ ๊ฐ™์ด ์ธ๋ผ์ธ์œผ๋กœ ์ž‘์„ฑ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. React๋Š” ๊ฐ ์˜์กด์„ฑ์„ Object.is ๋น„๊ต๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด์ „ ๊ฐ’๊ณผ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค. ๋ฆฌ๋ Œ๋”๋ง์œผ๋กœ ์ธํ•ด ์ผ๋ถ€ ์˜์กด์„ฑ์ด ๋ณ€๊ฒฝ๋˜๊ฑฐ๋‚˜ ์ด ์ธ์ˆ˜๋ฅผ ์ƒ๋žตํ•œ ๊ฒฝ์šฐ createHandleํ•จ์ˆ˜๊ฐ€ ๋‹ค์‹œ ์‹คํ–‰๋˜๊ณ  ์ƒˆ๋กœ ์ƒ์„ฑ๋œ ํ•ธ๋“ค์ด ref์— ํ• ๋‹น๋ฉ๋‹ˆ๋‹ค.

๋ฐ˜ํ™˜๊ฐ’

useImperativeHandle์€ undefined๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.


์‚ฌ์šฉ๋ฒ•

๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์— ์ปค์Šคํ…€ refํ•ธ๋“ค ๋…ธ์ถœ

๊ธฐ๋ณธ์ ์œผ๋กœ ์ปดํฌ๋„ŒํŠธ๋Š” ์ž์‹ ์ปดํฌ๋„ŒํŠธ์˜ DOM ๋…ธ๋“œ๋ฅผ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์— ๋…ธ์ถœํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด MyInput์˜ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๊ฐ€ <input> DOM ๋…ธ๋“œ์— ์ ‘๊ทผํ•˜๋ ค๋ฉด forwardRef๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์„ ํƒ์ ์œผ๋กœ ์ฐธ์กฐ์— ํฌํ•จํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

import { forwardRef } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
return <input {...props} ref={ref} />;
});

์œ„์˜ ์ฝ”๋“œ์—์„œ MyInput์— ๋Œ€ํ•œ ref๋Š” <input> DOM ๋…ธ๋“œ๋ฅผ ๋ฐ›๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋Œ€์‹  ์‚ฌ์šฉ์ž ์ง€์ • ๊ฐ’์„ ๋…ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋…ธ์ถœ๋œ ํ•ธ๋“ค์„ ์‚ฌ์šฉ์ž ์ •์˜ํ•˜๋ ค๋ฉด ์ปดํฌ๋„ŒํŠธ์˜ ์ตœ์ƒ์œ„ ๋ ˆ๋ฒจ์—์„œ useImperativeHandle์„ ํ˜ธ์ถœํ•˜์„ธ์š”.

import { forwardRef, useImperativeHandle } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
useImperativeHandle(ref, () => {
return {
// ... ๋ฉ”์„œ๋“œ๋ฅผ ์—ฌ๊ธฐ์— ์ž…๋ ฅํ•˜์„ธ์š” ...
};
}, []);

return <input {...props} />;
});

์œ„์˜ ์ฝ”๋“œ์—์„œ <input>์— ๋Œ€ํ•œ ref๋Š” ๋”์ด์ƒ ์ „๋‹ฌ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด ์ „์ฒด <input> DOM ๋…ธ๋“œ๋ฅผ ๋…ธ์ถœํ•˜์ง€ ์•Š๊ณ  focus์™€ scrollIntoView์˜ ๋‘ ๋ฉ”์„œ๋“œ๋งŒ์„ ๋…ธ์ถœํ•˜๊ณ  ์‹ถ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ด…์‹œ๋‹ค. ๊ทธ๋Ÿฌ๊ธฐ ์œ„ํ•ด์„œ๋Š” ์‹ค์ œ ๋ธŒ๋ผ์šฐ์ € DOM์„ ๋ณ„๋„์˜ ref์— ์œ ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  useImperativeHandle์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์—์„œ ํ˜ธ์ถœํ•  ๋ฉ”์„œ๋“œ๋งŒ ์žˆ๋Š” ํ•ธ๋“ค์„ ๋…ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

import { forwardRef, useRef, useImperativeHandle } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
const inputRef = useRef(null);

useImperativeHandle(ref, () => {
return {
focus() {
inputRef.current.focus();
},
scrollIntoView() {
inputRef.current.scrollIntoView();
},
};
}, []);

return <input {...props} ref={inputRef} />;
});

์ด์ œ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๊ฐ€ MyInput์— ๋Œ€ํ•œ ref๋ฅผ ๊ฐ€์ ธ์˜ค๋ฉด focus ๋ฐ scrollIntoView ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๊ธฐ๋ณธ <input> DOM ๋…ธ๋“œ์˜ ์ „์ฒด ์—‘์„ธ์Šค ๊ถŒํ•œ์€ ์—†์Šต๋‹ˆ๋‹ค.

import { useRef } from 'react';
import MyInput from './MyInput.js';

export default function Form() {
  const ref = useRef(null);

  function handleClick() {
    ref.current.focus();
    // ์ด ์ž‘์—…์€ DOM ๋…ธ๋“œ๊ฐ€ ๋…ธ์ถœ๋˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
    // ref.current.style.opacity = 0.5;
  }

  return (
    <form>
      <MyInput label="Enter your name:" ref={ref} />
      <button type="button" onClick={handleClick}>
        Edit
      </button>
    </form>
  );
}


์‚ฌ์šฉ์ž ์ •์˜ imperative ๋ฉ”์„œ๋“œ ๋…ธ์ถœ

imperative handle์„ ํ†ตํ•ด ๋…ธ์ถœํ•˜๋Š” ๋ฉ”์„œ๋“œ๋Š” DOM ๋ฉ”์„œ๋“œ์™€ ์ •ํ™•ํ•˜๊ฒŒ ์ผ์น˜ํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์ด Post ์ปดํฌ๋„ŒํŠธ๋Š” imperative handle์„ ํ†ตํ•ด scrollAndFocusAddComment ๋ฉ”์„œ๋“œ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ถ€๋ชจ Page์—์„œ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•  ๋•Œ ๋Œ“๊ธ€ ๋ชฉ๋ก์„ ์Šคํฌ๋กคํ•˜๊ณ  ์ž…๋ ฅ ํ•„๋“œ์— ์ดˆ์ ์„ ๋งž์ถœ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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

์ฃผ์˜ํ•˜์„ธ์š”!

ref๋ฅผ ๊ณผ๋„ํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜์ง€ ๋งˆ์„ธ์š”. ref๋Š” props๋กœ ํ‘œํ˜„ํ•  ์ˆ˜ ์—†๋Š” ํ•„์ˆ˜์ ์ธ ํ–‰๋™์—๋งŒ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ํŠน์ • ๋…ธ๋“œ๋กœ ์Šคํฌ๋กค ํ•˜๊ธฐ, ๋…ธ๋“œ์— ์ดˆ์  ๋งž์ถ”๊ธฐ, ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ด‰๋ฐœํ•˜๊ธฐ, ํ…์ŠคํŠธ ์„ ํƒํ•˜๊ธฐ ๋“ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

prop์œผ๋กœ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์€ ref๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ๋งˆ์„ธ์š”. ์˜ˆ๋ฅผ ๋“ค์–ด Modal ์ปดํฌ๋„ŒํŠธ์—์„œ { open, close } ์™€ ๊ฐ™์€ imperative handle์„ ๋…ธ์ถœํ•˜๋Š” ๋Œ€์‹  <Modal isOpen={isOpen} />๊ณผ ๊ฐ™์€ isOpen prop์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋” ์ข‹์Šต๋‹ˆ๋‹ค. Effects๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด prop์„ ํ†ตํ•ด ๋ช…๋ นํ˜• ๋™์ž‘(imperative behavior)์„ ๋…ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.