๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ“š Books/๐Ÿ“• Advanced React

[Ch9] Refs: from storing data to imperative API

by nalong 2025. 4. 13.

์ด๋ฒˆ ์žฅ์—์„œ๋Š” React์—์„œ DOM์— ์ง์ ‘ ์ ‘๊ทผํ•ด์•ผ ํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๋„๊ตฌ์ธ Ref์— ๋Œ€ํ•ด ๋‹ค๋ฃฌ๋‹ค.

React์˜ ํฐ ์žฅ์  ์ค‘ ํ•˜๋‚˜๋Š” ์‹ค์ œ DOM์„ ์ง์ ‘ ๋‹ค๋ฃจ๋Š” ๋ณต์žกํ•œ ๊ณผ์ •์„ ๊ฐœ๋ฐœ์ž๊ฐ€ ์‹ ๊ฒฝ ์“ฐ์ง€ ์•Š์•„๋„ ๋˜๋„๋ก ์ถ”์ƒํ™”ํ•ด ์ค€๋‹ค๋Š” ์ ์ด๋‹ค.  getElementById๋กœ DOM ์š”์†Œ๋ฅผ ์ฐพ๊ณ , ํด๋ž˜์Šค๋ฅผ ์ˆ˜๋™์œผ๋กœ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜, ๋ธŒ๋ผ์šฐ์ € ๊ฐ„์˜ ์ฐจ์ด๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ์ผ ์—†์ด, ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ๋งŒ์œผ๋กœ UI๋ฅผ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ํฌ์ปค์Šค๋ฅผ ์ฃผ๊ฑฐ๋‚˜, ์š”์†Œ๋ฅผ ์Šคํฌ๋กคํ•˜๊ฑฐ๋‚˜, ์‚ฌ์ด์ฆˆ๋ฅผ ๊ณ„์‚ฐํ•˜๋Š” ๋“ฑ์˜ ํŠน์ • ์ž‘์—…์—์„œ๋Š” ์‹ค์ œ DOM์— ์ง์ ‘ ์ ‘๊ทผํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์ƒ๊ธฐ๋Š”๋ฐ, ์ด๋•Œ ํ•„์š”ํ•œ ๋„๊ตฌ๊ฐ€ ๋ฐ”๋กœ Ref๋‹ค.

Ref vs State

๊ฒฐ๋ก ๋ถ€ํ„ฐ ๋งํ•˜๋ฉด Ref๋Š” ๊ฐ’์„ ์ €์žฅํ•˜์ง€๋งŒ, ๊ทธ ๊ฐ’์ด ๋ฐ”๋€๋‹ค๊ณ  ํ•ด์„œ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง ๋˜์ง„ ์•Š๋Š”๋‹ค. ๋ฐ˜๋ฉด state๋Š” ๊ฐ’์ด ๋ฐ”๋€Œ๋ฉด ๋ฆฌ๋ Œ๋”๋ง์„ ํŠธ๋ฆฌ๊ฑฐํ•œ๋‹ค. ๊ทธ๋ž˜์„œ ํ™”๋ฉด์— ๋ฌด์–ธ๊ฐ€ ๋ณด์—ฌ์ฃผ๊ฑฐ๋‚˜, ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ๋กœ ๊ฐ’์„ ์ „๋‹ฌํ•ด์•ผ ํ•œ๋‹ค๋ฉด state๋ฅผ ์จ์•ผ ํ•˜๊ณ , ๋‹จ์ˆœํžˆ ๋‚ด๋ถ€์—์„œ ์ €์žฅ๋งŒ ํ•  ๊ฑฐ๋ผ๋ฉด Ref๊ฐ€ ์ ํ•ฉํ•˜๋‹ค.

const Form = () => {
  const ref = useRef();
  const onChange = (e) => {
    ref.current = e.target.value;
  };
  const submit = () => {
    console.log(ref.current);
  };

  return (
    <>
      <input type="text" onChange={onChange} />
      <button onClick={submit}>submit</button>
    </>
  );
};

 

์œ„ ์˜ˆ์ œ๋Š” Ref๋ฅผ ํ†ตํ•ด ๊ฐ’์„ ์ €์žฅํ•˜๊ณ  ์ œ์ถœ ์‹œ์ ์— ์‚ฌ์šฉํ•˜๋Š”๋ฐ, ๋งŒ์•ฝ ํ™”๋ฉด์— ๊ฐ’์„ ๋ Œ๋”๋งํ•ด์•ผ ํ•œ๋‹ค๋ฉด Ref๋งŒ์œผ๋กœ๋Š” ์•ˆ ๋˜๊ณ  State๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

const numberOfLetters = ref.current?.length ?? 0;

 

๋งŒ์•ฝ ์œ„์™€ ๊ฐ™์ด ํ™”๋ฉด์— ๋ณด์—ฌ์ฃผ๋ฉด Ref๋Š” ๊ฐ’์ด ๋ณ€ํ•ด๋„ React๊ฐ€ ๋‹ค์‹œ ๋ Œ๋”๋ง ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— numberOfLetters ๊ฐ’์€ ํ•ญ์ƒ 0์œผ๋กœ ๋ณด์ธ๋‹ค.

๋น„์Šทํ•œ ๋งฅ๋ฝ์œผ๋กœ Ref ๊ฐ’์„ props๋กœ ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ์— ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ๋„ ๋™์ž‘ํ•˜์ง€ ์•Š๋Š”๋‹ค.

๋˜ ํ•˜๋‚˜ ์ค‘์š”ํ•œ ์ ์€ Ref๋Š” ๋™๊ธฐ์ ์œผ๋กœ ๊ฐ’์ด ๋ฐ˜์˜๋˜๊ณ , state๋Š” ๋น„๋™๊ธฐ์ ์œผ๋กœ ๋™์ž‘ํ•œ๋‹ค๋Š” ์ ์ด๋‹ค.

  const onChange = (e) => {
    console.log("state before:", value);
    setValue(e.target.value);
    console.log("state after:", value); // ์ด์ „ ๊ฐ’

    console.log("ref before:", ref.current);
    ref.current = e.target.value;
    console.log("ref after:", ref.current); // ๋ฐ”๋กœ ๋ฐ˜์˜๋จ
  };

  return (
    <div style={{ padding: 20 }}>
      <input type="text" onChange={onChange} placeholder="ํƒ€์ดํ•‘ ํ•ด๋ณด์„ธ์š”" />
    </div>
  );

 

crus๋ผ๊ณ  ์ž…๋ ฅ๋œ ์ƒํƒœ์—์„œ h๋ฅผ ์ž…๋ ฅํ•˜๋ฉด, Ref๋Š” crush๋กœ ๋ฐ”๋กœ ๋ฐ˜์˜๋˜์ง€๋งŒ, State๋Š” ์—ฌ์ „ํžˆ crus์ธ ์ƒํƒœ๋กœ ์ฝ˜์†”์— ์ฐํžŒ๋‹ค.
ref.current๋Š” ๋™๊ธฐ์ ์œผ๋กœ ๋ฐ”๋กœ ์—…๋ฐ์ดํŠธ๋˜๊ณ , useState๋Š” ๋‹ค์Œ ๋ Œ๋”๋ง ์ „๊นŒ์ง€ ๊ฐ’์ด ์œ ์ง€๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

Ref๋ฅผ ์ž์‹ ์ปดํฌ๋„ŒํŠธ์— ์ „๋‹ฌํ•˜๊ธฐ (forwardRef)

Ref๋ฅผ ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ์— ์ „๋‹ฌํ•˜๋ ค๋ฉด props์ฒ˜๋Ÿผ ๋„˜๊ธฐ๋ฉด ์•ˆ ๋˜๊ณ  forwardRef๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์—์„œ ์ž์‹ ์ปดํฌ๋„ŒํŠธ์˜ input DOM์— ์ง์ ‘ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค.

const InputField = forwardRef((props, ref) => {
  return <input ref={ref} />;
});
const Form = () => {
  const inputRef = useRef(null);

  return <InputField ref={inputRef} />;
};

 

imperative API ๊ตฌ์„ฑํ•˜๊ธฐ (useImperativeHandle)

์˜ˆ๋ฅผ ๋“ค์–ด, ์—๋Ÿฌ ์ƒํ™ฉ์—์„œ input์„ ํ”๋“ค์–ด ์‚ฌ์šฉ์ž์—๊ฒŒ ์‹œ๊ฐ์ ์œผ๋กœ ์•Œ๋ฆฌ๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค๊ณ  ํ•ด๋ณด์ž. ์ด๋Ÿฐ ๋™์ž‘์€ ๋‹จ์ˆœํžˆ ์ƒํƒœ๋งŒ์œผ๋กœ๋Š” ์ฒ˜๋ฆฌํ•˜๊ธฐ ์–ด๋ ค์šฐ๋ฉฐ, ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ž์‹ ์ปดํฌ๋„ŒํŠธ์˜ ๋ฉ”์„œ๋“œ๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•ด์•ผ ํ•˜๋Š”๋ฐ, ์ด๋•Œ ํ•„์š”ํ•œ ๊ฒƒ์ด ๋ฐ”๋กœ imperative API๋‹ค.

useImperativeHandle ์€ imperative API๋ฅผ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” React ํ›…์ธ๋ฐ, ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋Š” ํ•ด๋‹น ํ›…์„ ํ†ตํ•ด ์™ธ๋ถ€์— ํ•„์š”ํ•œ ๋ฉ”์„œ๋“œ๋งŒ ์„ ํƒ์ ์œผ๋กœ ๋…ธ์ถœํ•  ์ˆ˜ ์žˆ๊ณ , ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๋Š” ํ•ด๋‹น ๋ฉ”์„œ๋“œ๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋‚ด๋ถ€์˜ DOM ๊ตฌ์กฐ๋‚˜ ๊ตฌํ˜„ ์„ธ๋ถ€์‚ฌํ•ญ์€ ๊ฐ์ถ˜ ์ฑ„, ํ•„์š”ํ•œ ๋™์ž‘๋งŒ ์™ธ๋ถ€์— ์•ˆ์ „ํ•˜๊ฒŒ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์—์„œ ์˜๋ฏธ๊ฐ€ ์žˆ๋‹ค.

const InputField = ({ apiRef }) => {
  const inputRef = useRef(null);
  const [shouldShake, setShouldShake] = useState(false);

  useImperativeHandle(apiRef, () => ({
    focus: () => inputRef.current.focus(),
    shake: () => setShouldShake(true),
  }), []);

  return (
    <input
      ref={inputRef}
      className={shouldShake ? 'shake-animation' : ''}
      onAnimationEnd={() => setShouldShake(false)}
    />
  );
};

 

๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์—์„œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

const Form = () => {
  const inputRef = useRef(null);
  const [name, setName] = useState('');

  const onSubmitClick = () => {
    if (!name) {
      inputRef.current.focus();
      inputRef.current.shake();
    } else {
      // submit logic
    }
  };

  return (
    <>
      <InputField apiRef={inputRef} onChange={setName} />
      <button onClick={onSubmitClick}>Submit the form!</button>
    </>
  );
};

 

๊ทผ๋ฐ ๊ผญ useImperativeHandle์„ ์จ์•ผ ํ•˜๋Š” ๊ฑด ์•„๋‹ˆ๋‹ค. useEffect ์•ˆ์—์„œ ref์— ์ง์ ‘ ํ•จ์ˆ˜ ๊ฐ์ฒด๋ฅผ ํ• ๋‹นํ•ด๋„ ๋˜‘๊ฐ™์ด ์ž‘๋™ํ•œ๋‹ค.

const InputField = ({ apiRef }) => {
  const inputRef = useRef(null);

  useEffect(() => {
    apiRef.current = {
      focus: () => inputRef.current.focus(),
      shake: () => {/* setState ๋“ฑ */},
    };
  }, [apiRef]);

  return <input ref={inputRef} />;
};

 

๋‹ค๋งŒ useImperativeHandle์„ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • ๋ช…ํ™•ํ•œ ์˜๋„ ํ‘œํ˜„: ์ด ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์™ธ๋ถ€์— ํŠน์ • ๋ช…๋ นํ˜• API๋ฅผ ์ œ๊ณตํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์ฝ”๋“œ๋งŒ ๋ด๋„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.
  • ๋ถˆํ•„์š”ํ•œ ์žฌํ• ๋‹น ๋ฐฉ์ง€: useEffect ๋ฐฉ์‹์€ ์˜์กด์„ฑ ๋ฐฐ์—ด์— ๋”ฐ๋ผ API ๊ฐ์ฒด๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง๋งˆ๋‹ค ์žฌ์ƒ์„ฑ๋  ์ˆ˜ ์žˆ๋Š”๋ฐ, useImperativeHandle์€ ์ด๋Ÿฐ ๋ถˆํ•„์š”ํ•œ ํ• ๋‹น์„ ๋ฐฉ์ง€ํ•˜๋Š” ๋ฐ ๋” ์ ์ ˆํ•˜๋‹ค.

๋‹จ์ˆœํžˆ ๋™์ž‘ํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” ๊ฒƒ๋งŒ ๋ชฉ์ ์ด๋ผ๋ฉด useEffect๋กœ๋„ ์ถฉ๋ถ„ํ•˜์ง€๋งŒ, ์˜๋„๋ฅผ ๋ช…ํ™•ํžˆ ํ•˜๊ณ  ์ปดํฌ๋„ŒํŠธ์˜ ์บก์Аํ™”๋ฅผ ์œ ์ง€ํ•˜๊ณ ์ž ํ•œ๋‹ค๋ฉด useImperativeHandle์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋” ์•ˆ์ „ํ•˜๊ณ  ๊ถŒ์žฅ๋˜๋Š” ๋ฐฉ์‹์ด๋ผ๊ณ  ํ•œ๋‹ค.

๋А๋‚€ ์ 

๋‚˜๋„ ์Šคํฌ๋กค ์œ„์น˜๋ฅผ ๊ณ„์‚ฐํ•˜๊ฑฐ๋‚˜ ์œ ๋™์ ์ธ width ๊ฐ’์„ ์ฒ˜๋ฆฌํ•  ๋•Œ ref๋ฅผ ์‚ฌ์šฉํ–ˆ์—ˆ๋Š”๋ฐ, ์ด๋ฒˆ ์žฅ์„ ์ฝ์œผ๋ฉฐ ๋‹ค์‹œ ํ•œ๋ฒˆ ref์˜ ์—ญํ• ๊ณผ ์ ์ ˆํ•œ ์‚ฌ์šฉ๋ฐฉ๋ฒ•์„ ๋ช…ํ™•ํ•˜๊ฒŒ ์ •๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด ์ข‹์•˜๋‹ค.

์ •๋ฆฌํ•˜์ž๋ฉด

  • ๋ Œ๋”๋ง์— ์˜ํ–ฅ์„ ์ฃผ๋Š” ๊ฐ’์ด๋ผ๋ฉด ๋ฌด์กฐ๊ฑด state๋ฅผ ์“ฐ์ž.
  • ์™ธ๋ถ€์— ๋…ธ์ถœ๋˜๋ฉด ์•ˆ ๋˜๋Š” ๋‚ด๋ถ€ ๋™์ž‘์€ useImperativeHandle๋กœ ๊ฐ์ถœ ์ˆ˜ ์žˆ๋‹ค.
  • ๋‹จ์ˆœํžˆ ์ €์žฅ๋งŒ ํ•˜๊ฑฐ๋‚˜ ํŠน์ • ์‹œ์ ์—๋งŒ ๋™์ž‘์„ ํŠธ๋ฆฌ๊ฑฐํ•˜๋Š” ๊ฐ’์€ ref๊ฐ€ ์ ํ•ฉํ•˜๋‹ค.

๊ทธ๋ฆฌ๊ณ , ๊ณต์‹ ๋ฌธ์„œ์—์„œ๋„ ref ์‚ฌ์šฉ ์‹œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ฃผ์˜์‚ฌํ•ญ์„ ๋ช…ํ™•ํžˆ ๋ช…์‹œํ•˜๊ณ  ์žˆ๋‹ค.

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

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

'๐Ÿ“š Books > ๐Ÿ“• Advanced React' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[Ch8] React Context and Performance  (0) 2025.04.06
[Ch7] Higher-order components in modern world  (0) 2025.02.23
[ch6] Diffing, Reconciliation  (0) 2025.02.16
[Ch5] Memoization with useMemo, useCallback and React.memo  (1) 2025.02.09
[Ch3 - Ch4] Elements as Props์™€ Render Props  (0) 2025.02.02