ref 콜백

렐루·2024년 5월 21일
0

리액트

목록 보기
15/20

1. 리액트에서의 ref

https://ko.react.dev/learn/manipulating-the-dom-with-refs
공식문서를 읽어가며 리액트의 기초를 공부하는 중에 너무 새로운 것을 배우게 됐습니다.

돔 엘리먼트의 ref 프로퍼티가 콜백을 받는 모습에서 뭔가 제가 잘못 알고 있음을 깨닫게 되었습니다..

먼저 간단하게 ref 프로퍼티를 정리하겠습니다.

ref 프로퍼티

ref 프로퍼티를 사용하는 이유

React는 렌더링 결과물에 맞춰 DOM 변경을 자동으로 처리하기 때문에 컴포넌트에서 자주 DOM을 조작해야 할 필요는 없습니다. 하지만 가끔 특정 노드에 포커스를 옮기거나, 스크롤 위치를 옮기거나, 위치와 크기를 측정하기 위해서 React가 관리하는 DOM 요소에 접근해야 할 때가 있습니다. React는 이런 작업을 수행하는 내장 방법을 제공하지 않기 때문에 DOM 노드에 접근하기 위한 ref가 필요할 것입니다.

단순한 랜더링으로 화면을 그리고 수정하는 것을 넘어서서 좀더 디테일하고 세세한 작업을 추가로 하고 싶을 때에 사용한다고 생각합니다.
일반적인 다른 훅들이나 props로 할 수 있는 랜더링 과정은 최대한 ref의 사용을 피해야 합니다. 하지만 공식문서의 설명과 같이 위치나 크기, 스크롤 등의 랜더링의 범주 외에서는 편리하게 사용할 수 있습니다.

ref 콜백

React는 ref를 설정할 때 DOM 노드와 함께 ref 콜백을 호출하며, ref를 지울 때에는 null을 전달합니다. 이를 통해 자체 배열이나 Map을 유지하고, 인덱스나 특정 ID를 사용하여 어떤 ref에든 접근할 수 있습니다.

위의 설명은 공식문서의 설명이고 아래의 설명은 지씨의 설명입니다.

컴포넌트가 마운트(화면에 나타남)될 때 해당 DOM 요소를 인자로 받고, 언마운트(화면에서 사라짐)될 때는 null을 인자로 받습니다. 함수의 인자로 받는 것은 해당 DOM 요소 자체이며, 이를 통해 직접적으로 DOM 조작이 가능하게 됩니다.

2. 코드 전체

import { useRef, useState } from "react";

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) {
      // 처음 사용하는 경우, Map을 초기화합니다.
      itemsRef.current = new Map();
    }
    return itemsRef.current;
  }

  return (
    <>
      <nav>
        <button onClick={() => scrollToCat(catList[0])}>Tom</button>
        <button onClick={() => scrollToCat(catList[5])}>Maru</button>
        <button onClick={() => scrollToCat(catList[9])}>Jellylorum</button>
      </nav>
      <div>
        <ul>
          {catList.map((cat) => (
            <li
              key={cat}
              ref={(node) => {
                const map = getMap();
                if (node) {
                  map.set(cat, node);
                } else {
                  map.delete(cat);
                }
              }}
            >
              <img src={cat} />
            </li>
          ))}
        </ul>
      </div>
    </>
  );
}

function setupCatList() {
  const catList = [];
  for (let i = 0; i < 10; i++) {
    catList.push("https://loremflickr.com/320/240/cat?lock=" + i);
  }

  return catList;
}

위의 코드에서 처음에 이해가 가지 않았던 부분은
ref의 콜백과 콜백의 인자 node 였습니다.

ref={(node) => {
  const map = getMap();
  if (node) {
    map.set(cat, node);
  } else {
    map.delete(cat);
  }
}}

위의 설명을 기반으로 코드를 이해해보겠습니다.
ref의 콜백은 해당 엘리먼트가 마운트 되면 자기 자신을, 언마운트 되면 null을 인자로 줍니다.

따라서 if문의 node는 랜더링되어 보이면 자기 자신, 아니면 null의 조건으로 동작하게 됩니다.
그래서 true 이면 map.set(cat, node);
Map 자료구조에 자기 자신을 벨류로 저장하고 없으면 Map에서 삭제하는 코드입니다.

위와 같이 코드를 작성하면 개별 노드를 쉽게 찾아서 접근할 수 있습니다.
getMap 함수는 useRef로 저장되어 있는 Map에 접근할 수 있게 해주는 함수로 어디서든 함수를 호출해서 li 엘리먼트에 접근함으로서 위의 코드에서는 스크롤을 편하게 변경할 수 있게 해줍니다.

3. scrollIntoView

https://developer.mozilla.org/ko/docs/Web/API/Element/scrollIntoView

scrollIntoView()가 호출 된 요소가 사용자에게 표시되도록 요소의 상위 컨테이너를 스크롤합니다.

위의 코드에서 사용한 web api 입니다.

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

코드에서는 옵션 값을 객체로 설정하고 있습니다.
돔 엘리먼트에서 호출하여 해당 엘리먼트의 부모 엘리먼트에서 스크롤함으로 위치를 이동시킬 수 있습니다.

위의 코드에서는 부드럽게, 수직방향에서는 그냥 내 스크롤에서 보이는 선에서 가장 가까이, 수평은 가운데로 입니다.
만약에 다른 값 처음이나 끝 가운데로 설정하면 제가 변경할때마다 해당 스크롤 위치로 상하가 움직이므로 살짝 불편함을 느낄 수 있습니다.

리액트에서 제공한 예시로 다양한 것들을 배울 수 있는 시간이었습니다.
감사합니다.

profile
프론트 공부중입니다!

0개의 댓글