Manipulating the Dom with Refs

date
2026-01-22
order
2
link
  • Manipulating the DOM with Refs โ€“ React

You will learn

  • ref๋กœ ๋ฆฌ์•กํŠธ๊ฐ€ ๊ด€๋ฆฌํ•˜๋Š” DOM ๋…ธ๋“œ์— ์ ‘๊ทผํ•˜๋Š” ๋ฐฉ๋ฒ•
  • ref์™€ useRefํ›…์˜ ๊ด€๊ณ„
  • ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ์˜ DOM ๋…ธ๋“œ์— ์ ‘๊ทผํ•˜๋Š” ๋ฐฉ๋ฒ•
  • ์–ธ์ œ ๋ฆฌ์•กํŠธ๊ฐ€ ๊ด€๋ฆฌํ•˜๋Š” DOM์„ ์ˆ˜์ •ํ•ด๋„ ๋˜๋Š”์ง€

DOM ๋…ธ๋“œ ์กฐ์ž‘

๊ธฐ๋ณธ์ ์œผ๋กœ ๋ฆฌ์•กํŠธ๋Š” DOM ์กฐ์ž‘์„ ๊ฐœ๋ฐœ์ž ๋Œ€์‹  ํ•ด์ค€๋‹ค. ์„ ์–ธํ˜• ๋ฐฉ์‹์œผ๋กœ UI๊ฐ€ ์–ด๋–ป๊ฒŒ ๊ทธ๋ ค์ ธ์•ผ ํ•˜๋Š”์ง€์— ๋Œ€ํ•œ ์„ค๊ณ„๋„๋งŒ ์ž‘์„ฑํ•˜๋ฉด ๋ฆฌ์•กํŠธ๊ฐ€ ๋งค ์ˆœ๊ฐ„ ๊ทธ์— ์•Œ๋งž๊ฒŒ ๊ทธ๋ ค์ฃผ๋Š”๊ฒƒ์ธ๋ฐ, ๊ทธ๋ž˜์„œ ํŠน์ •ํ•œ ๋ฐฉ๋ฒ•์„ ํ†ตํ•ด์„œ๋งŒ ๋ฆฌ์•กํŠธ ๋ฐ–์˜ ์š”์†Œ๋“ค์„ ์กฐ์ข…ํ•  ์ˆ˜ ์žˆ๋‹ค.

๊ณต์‹ ๋ฌธ์„œ์—์„œ๋Š” ํƒˆ์ถœ๊ตฌ "Escape Hatches" ๋ผ๊ณ  ํ‘œํ˜„ํ•˜๋Š”๋ฐ ๋ง ๊ทธ๋Œ€๋กœ ๋ฆฌ์•กํŠธ ๋ฐ–์— ๋‚˜๊ฐ€๊ฒŒ ํ•ด์ฃผ๋Š” ๋ฐฉ๋ฒ•๋“ค์ด๋‹ค. ์—ฌ๊ธฐ์—๋Š” ref์™€ effect ๋‘ ๋ฐฉ์‹์ด ์žˆ๋Š”๋ฐ ref๋กœ๋Š” ์™ธ๋ถ€์— ์ง์ ‘์ ์œผ๋กœ ์ ‘๊ทผํ•ด์„œ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๊ณ , effect๋กœ๋Š” ๋ฆฌ์•กํŠธ ์ปดํฌ๋„ŒํŠธ์™€ ์™ธ๋ถ€ ์‹œ์Šคํ…œ์„ ๋™๊ธฐํ™” ํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋‘˜์ด ํ•จ๊ป˜ ์‚ฌ์šฉ๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งค์šฐ ์žฆ๋‹ค.

const myRef = useRef(null);

//...

<div ref={myRef} />

์ด๋ ‡๊ฒŒ ์ฐธ์กฐํ•  ๋…ธ๋“œ์— ref๋ฅผ ๊ฑด๋‚ด์ฃผ๋ฉด ๋ฆฌ์•กํŠธ๋Š” myRef.current์— ์ด ๋…ธ๋“œ๋ฅผ ๋„ฃ๋Š”๋‹ค. ๊ทธ๋ฆฌ๊ณ  myRef.current.scrollIntoView() ์ฒ˜๋Ÿผ div ๋…ธ๋“œ์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ธŒ๋ผ์šฐ์ € API๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

ref ์ฝœ๋ฐฑ

์‚ฌ์ „์— ๋ฏธ๋ฆฌ ๋ช‡๊ฐœ์˜ ref๊ฐ€ ํ•„์š”ํ•œ์ง€ ์•Œ ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ๋„ ๋งŽ์„๊ฒƒ์ด๋‹ค. items ์˜ ๊ฐœ์ˆ˜๋งŒํผ ref ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผํ• ๊นŒ?

์ฒซ ๋ฒˆ์งธ ๋ฐฉ๋ฒ•์€, items ๋ฅผ ๋‹ด์„ ๋ถ€๋ชจ ๋…ธ๋“œ๋ฅผ ๋งŒ๋“ค๊ณ  ๊ทธ ๋ถ€๋ชจ ๋…ธ๋“œ์— ๋Œ€ํ•œ ref ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด ref.current.querySelectorAll()๋กœ ์ž์‹ ๋…ธ๋“œ๋ฅผ ์ฐพ๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค.

๋‘ ๋ฒˆ์งธ ๋ฐฉ๋ฒ•์€ ref ์ฝœ๋ฐฑ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์œผ๋กœ ์ง€์ •ํ•  ๋…ธ๋“œ์˜ ref ์†์„ฑ์— ref ์ฝœ๋ฐฑํ•จ์ˆ˜๋ฅผ ๋„ฃ๋Š”๋ฐฉ๋ฒ•์ด๋‹ค.

export default function CatFriends() {
  const itemsRef = useRef(null);
  const [catList, setCatList] = useState(setupCatList);

  function scrollToCat(cat) {
    const map = getMap();
    const node = map.get(cat);
    node.scrollIntoView({
      behavior: "smooth",
      block: "nearest",
      inline: "center",
    });
  }

  function getMap() {
    if (!itemsRef.current) {
      // Initialize the Map on first usage.
      itemsRef.current = new Map();
    }
    return itemsRef.current;
  }

  return (
    <>
      <nav>
        <button onClick={() => scrollToCat(catList[0])}>
	        Neo
        </button>
        <button onClick={() => scrollToCat(catList[5])}>
	        Millie
        </button>
        <button onClick={() => scrollToCat(catList[8])}>
	        Bella
        </button>
      </nav>
      <div>
        <ul>
          {catList.map((cat) => (
            <li
              key={cat.id}
              ref={(node) => {
                const map = getMap();
                map.set(cat, node);

                return () => {
                  map.delete(cat);
                };
              }}
            >
              <img src={cat.imageUrl} />
            </li>
          ))}
        </ul>
      </div>
    </>
  );
}

function setupCatList() {
  const catCount = 10;
  const catList = new Array(catCount)
  for (let i = 0; i < catCount; i++) {
    let imageUrl = '';
    if (i < 5) {
      imageUrl = "https://placecats.com/neo/320/240";
    } else if (i < 8) {
      imageUrl = "https://placecats.com/millie/320/240";
    } else {
      imageUrl = "https://placecats.com/bella/320/240";
    }
    catList[i] = {
      id: i,
      imageUrl,
    };
  }
  return catList;
}

์œ„์˜ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด itemsRef์— ๋น„์–ด์žˆ๋Š” map์„ ์ง€์ •ํ•œ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ Œ๋”๋ง์ด ๋๋‚˜๊ณ  ๊ฐ li ๋…ธ๋“œ๋ณ„๋กœ ref๋ฅผ ์ง€์ •ํ•  ๋•Œ ์•ˆ์— ์žˆ๋Š” ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰์ด ๋˜๋ฉด์„œ map์— cat๊ฐ์ฒด๊ฐ€ ํ‚ค๋กœ ๊ฐ li ๋…ธ๋“œ๋“ค์ด ๊ฐ’์œผ๋กœ ๋“ค์–ด๊ฐ€๊ฒŒ ๋œ๋‹ค.

์ฆ‰ ๋‘ ๋ฐฉ๋ฒ• ๋ชจ๋‘ ์œ ๋™์ ์ธ ๊ฐœ์ˆ˜๋Œ€๋กœ ref๋ฅผ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ํ•˜๋‚˜์˜ ref ์•ˆ์— ๋„ฃ๋Š” ๋ฐฉ์‹์œผ๋กœ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š”๋ฐ, ์ด๊ฒŒ ๊ฐ€๋Šฅํ•œ ์ด์œ ๋Š” ref๊ฐ€ ๋ชจ๋“  ์ข…๋ฅ˜์˜ ๊ฐ’์„ ๋‹ด์„ ์ˆ˜ ์žˆ๋Š” ๊ฐ์ฒด์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ์˜ DOM ๋…ธ๋“œ ์ ‘๊ทผํ•˜๊ธฐ

์œ„์˜ ์˜ˆ์‹œ๋Š” ํ•œ ์ปดํฌ๋„ŒํŠธ ์•ˆ์—์„œ DOM ๋…ธ๋“œ๋ฅผ ์กฐ์ข…ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ–ˆ๋Š”๋ฐ, ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ์—์„œ๋„ ref๋ฅผ ์ง€์ •ํ•œ ๋…ธ๋“œ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค.

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

React 19 ์ด์ „๊นŒ์ง€๋Š” forwardRef ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ–ˆ์œผ๋‚˜ ์ด์ œ๋Š” ref๋ฅผ prop์œผ๋กœ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋๋‹ค.

useImperativeHandle๋กœ ์ ‘๊ทผ ๋ฒ”์œ„ ์ œํ•œํ•˜๊ธฐ

function MyInput({ ref }) {
  const realInputRef = useRef(null);
  useImperativeHandle(ref, () => ({
    // Only expose focus and nothing else
    focus() {
      realInputRef.current.focus();
    },
  }));
  return <input ref={realInputRef} />;
};

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

  function handleClick() {
    inputRef.current.focus();
  }

  return (
    <>
      <MyInput ref={inputRef} />
      <button onClick={handleClick}>Focus the input</button>
    </>
  );
}

์ด๋ ‡๊ฒŒ ๋ถ€๋ชจ์—๊ฒŒ ์ž๊ธฐ ๋…ธ๋“œ๋ฅผ ref๋กœ ๋„˜๊ฒจ์ฃผ๊ณ  ์žˆ๋Š” MyInput ์ปดํฌ๋„ŒํŠธ๋Š” ๋ถ€๋ชจ๊ฐ€ ์ •ํ•ด์ง„ ํ–‰๋™๋งŒ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ์ œํ•œํ•  ์ˆ˜ ์žˆ๋‹ค. ์—ฌ๊ธฐ์„œ ์ •ํ™•ํžˆ๋Š” input ๋…ธ๋“œ์˜ ref์ธ realInputRef๋Š” ์ž์‹ ์ปดํฌ๋„ŒํŠธ ์•ˆ์— ๋จธ๋ฌผ๊ณ  ๋ถ€๋ชจ๊ฐ€ ์ ‘๊ทผํ•˜๋Š” inputRef.current๋Š” ์ž์‹ ์ปดํฌ๋„ŒํŠธ์—์„œ useImperativeHandle๋กœ ๋งŒ๋“  ํŠน์ˆ˜ํ•œ ๊ฐ์ฒด๋‹ค.

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

์–ธ์ œ ref๊ฐ€ ์—ฐ๊ฒฐ๋˜๋Š”๊ฐ€

๋ฆฌ์•กํŠธ์—์„œ ํ™”๋ฉด์„ ์—…๋ฐ์ดํŠธ ํ•˜๋Š” ๊ณผ์ •์€ ๋ Œ๋”์™€ ์ปค๋ฐ‹ ๋‘ ๋‹จ๊ณ„๋กœ ๋‚˜๋‰œ๋‹ค. ๋ Œ๋” ๋‹จ๊ณ„์—์„œ๋Š” ์ปดํฌ๋„ŒํŠธ๋“ค์„ ์ฝ์–ด ๊ฐ€์ƒ DOM ํŠธ๋ฆฌ๋ฅผ ๋งŒ๋“ค๊ณ , ์ปค๋ฐ‹ ๋‹จ๊ณ„์—์„œ ํ™”๋ฉด์— ์ ์šฉํ•œ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ref๋Š” ์ปค๋ฐ‹๋‹จ๊ณ„์—์„œ ๋…ธ๋“œ๋“ค๊ณผ ์—ฐ๊ฒฐ๋œ๋‹ค. ์ด๋Š” ์ฆ‰ ref๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋‚˜ ์ดํŽ™ํŠธ์ฒ˜๋Ÿผ, ํ™”๋ฉด์ด ๋‹ค ๊ทธ๋ ค์ ธ์•ผ ์‹คํ–‰๋  ์ˆ˜ ์žˆ๋Š” ํ›…๊ณผ ํ•จ์ˆ˜์—์„œ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค๋Š” ์˜๋ฏธ๋‹ค.

flushSync , ๋™๊ธฐ ๋ฐฉ์‹์œผ๋กœ ์ƒํƒœ ์—…๋ฐ์ดํŠธ

function handleAdd() {
    const newTodo = { id: nextId++, text: text };
    setText('');
    setTodos([ ...todos, newTodo]);
    listRef.current.lastChild.scrollIntoView({
      behavior: 'smooth',
      block: 'nearest'
    });
  }

์œ„์˜ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด ์ƒˆ๋กœ์šด todo๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ๊ฐ€์žฅ ๋งˆ์ง€๋ง‰์— ์ถ”๊ฐ€๋œ todo, ์ฆ‰ ์ง€๊ธˆ ์ถ”๊ฐ€ํ•  todo๋กœ ์Šคํฌ๋กค์„ ์ด๋™ํ•˜๋Š” ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ผ๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ์—ฌ๊ธฐ์—๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ๋Š”๋ฐ, ์ƒํƒœ ์—…๋ฐ์ดํŠธ๊ฐ€ ๋ฆฌ์•กํŠธ์˜ ๋ฐฐ์นญ ์ฒ˜๋ฆฌ ๋ฐฉ์‹ ๋•Œ๋ฌธ์— '์ฆ‰์‹œ' ์ˆ˜ํ–‰๋˜๋Š”๊ฒŒ ์•„๋‹ˆ๋ผ๋Š” ์ ์ด๋‹ค. setTodo ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉด ์ˆœ์‹๊ฐ„์— ์ƒˆ๋กœ์šด todo ๋…ธ๋“œ๊ฐ€ ๋งŒ๋“ค์–ด์ง€๊ธด ํ•˜์ง€๋งŒ ๋ฐ”๋กœ ๋‹ค์Œ์—์„œ listRef.current ๋กœ ๋ฆฌ์ŠคํŠธ ๋…ธ๋“œ์— ์ ‘๊ทผํ• ๋•Œ๊นŒ์ง€๋Š” ๋งŒ๋“ค์–ด์ง€์ง€ ์•Š๋Š”๋‹ค. ๊ทธ๋ž˜์„œ ์ด์ „์— ๋งŒ๋“ค์–ด์ง„ todo, ํ•œ ์นธ์”ฉ ๋ฐ€๋ ค์„œ ์Šคํฌ๋กค์ด ์ด๋™ํ•˜๊ฒŒ ๋˜๋Š”๊ฒƒ์ด๋‹ค.

  function handleAdd() {
    const newTodo = { id: nextId++, text: text };
    flushSync(() => {
      setText('');
      setTodos([ ...todos, newTodo]);
    });
    listRef.current.lastChild.scrollIntoView({
      behavior: 'smooth',
      block: 'nearest'
    });
  }

์ด๋ ‡๊ฒŒ flushSync๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ƒํƒœ ์—…๋ฐ์ดํŠธ๋ฅผ '์ฆ‰์‹œ' ์ง„ํ–‰ํ•˜๊ณ  ๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ์ฝ๊ธฐ ๋•Œ๋ฌธ์— ๋ฌธ์ œ๊ฐ€ ํ•ด๊ฒฐ๋œ๋‹ค. ์ด๋ฅผํ…Œ๋ฉด await๊ณผ ๋น„์Šทํ•˜๋‹ค๊ณ  ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

ํ•˜์ง€๋งŒ ๋ฆฌ์•กํŠธ๋ฅผ ๊ฐœ๋ฐœํ•˜๋ฉด์„œ ๊ดœํžˆ ๋ฆฌ๋ Œ๋”๋ง์„ ๋ฐฐ์นญ ์ฒ˜๋ฆฌ ๋ฐฉ์‹์œผ๋กœ ์„ค์ •ํ•œ๊ฒƒ์€ ์•„๋‹๊ฒƒ์ด๋‹ค. ๋”ฐ๋ผ์„œ ์ด ๋ฐฐ์นญ ๋ฐฉ์‹์„ ๊ฐ•์ œ๋กœ ๋ฌด์‹œํ•˜๋Š” flushSync๋Š” ์ตœ์†Œํ•œ์œผ๋กœ ์‚ฌ์šฉ๋˜์–ด์•ผ ํ•  ๊ฒƒ์ด๋‹ค.

์˜ˆ์‹œ ๋ฌธ์ œ

1,2,4๋ฒˆ ๋ฌธ์ œ๋Š” ์‰ฌ์›Œ์„œ ์ƒ๋žต

3. ์ด๋ฏธ์ง€ ์Šคํฌ๋กค

export default function CatFriends() {
  const selectedRef = useRef(null);
  const [index, setIndex] = useState(0);

  return (
    <>
      <nav>
        <button onClick={() => {
          flushSync(() => {
            if (index < catList.length - 1) {
              setIndex(index + 1);
            } else {
              setIndex(0);
            }
          });
          selectedRef.current.scrollIntoView({
            behavior: 'smooth',
            block: 'nearest',
            inline: 'center'
          });
        }}>
          Next
        </button>
      </nav>
      <div>
        <ul>
          {catList.map((cat, i) => (
            <li
              key={cat.id}
              ref={index === i ?
                selectedRef :
                null
              }
            >
              <img
                className={
                  index === i ?
                    'active'
                    : ''
                }
                src={cat.imageUrl}
                alt={'Cat #' + cat.id}
              />
            </li>
          ))}
        </ul>
      </div>
    </>
  );
}

Next ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด index๊ฐ€ flushSync๋กœ ์—…๋ฐ์ดํŠธ๋˜๋ฉด์„œ ๋ฆฌ๋ Œ๋”๋ง -> index์™€ ์ผ์น˜ํ•˜๋Š” ๋…ธ๋“œ์— selectedRef ์—ฐ๊ฒฐ -> scollIntoView