React - useRef

goodjam92·2023년 3월 29일
0

React

목록 보기
5/7
post-thumbnail

React hook 알아보기 세 번째 hook은 useRef이다.
많이 사용해보진 않았지만 내가 알고 있던 이 hook의 특징은 DOM요소에 접근해야할 때 사용하는 것으로만 알고 있지만 이번에 한 번 제대로 알아보고 가보자!

useRef

DOM 노드 액세스

먼저 DOM 노드에 접근하려면 구성 요소 내부에 ref를 선언 후 DOM노드에 ref 속성으로 전달한다.

import {useRef} from "react";

function MyComponent(props) {
  const myRef = useRef(null);
  
  return <div ref={myRef} />
}

useRefcurrent라는 단일 속성을 가진 객체를 반환한다. myRef.current의 초기값은 null이며, 리액트가 <div>에 대한 DOM노드를 생성하면 리액트는 이 노드에 대한 참조를 myRef.current에 작성하게 된다.
그리고나서 이 노드에 대해 내장 브라우저 API를 사용할 수 있게 된다.

myRef.current.focus(); 
  • useRef를 사용한 대표적인 예
import {useRef} from 'react';

function MyInputComponent(props) {
  const inputRef = useRef(null);
  
  return (
    <div>
    	<input ref={inputRef} />
		<button onClick={()=> inputRef.current.focus();}>
          Focus input
        </button>
    </div>
  );
}

이 예시의 코드를 작성해서 화면에 있는 버튼을 눌러보면 input요소에 focus되는 것을 확인할 수 있다. 이처럼 DOM조작은 useRef의 가장 일반적인 사용 사례이다.
또한 scroll 구현 시 원하는 노드의 위치로 이동할 수 있는 버튼을 만든다거나, 화면을 이동할 때 DOM노드를 감지하여 액션을 하는 등에 사용할 수 있을 것이다.

* useRef와 IntersectionObserver를 활용한 화면

function MyComponent(props) {
  const [visible, setVisible] = useState(false);
  const pageRef = useRef(null);
  
  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => { // IntersectionObserverEntry의 속성이 담긴 배열
        if (!entry.isIntersecting) { // 관찰 대상이 화면과 교차 상태에서 나갈 때
          setVisible(false);
          return;
        }
        setVisible(entry.isIntersecting);// 관찰 대상이 화면과 교차 상태일 때
      },
      {
        threshold: 0.9, 
		// 옵저버가 실행되기 위해 타겟의 가시성이 얼마나 필요한지의 값
        // 0.9 => 90% 보여져야 옵저버가 실행
      }
    );
    if (ref.current) { 
      observer.observe(ref.current);
      return;
    }
    return () => {
      if (ref.current) { // 화면을 벗어날 때 opserver 제거
        observer.unobserve(ref.current);
      }
    };
  }, [ref]);
}


return (
  <div ref={pageRef}> 
	{visible === true ? children : null} 
  </div>
  )

이런식으로 useRef와 사용자 화면에 지금 보이는 요소인지 아닌지 구별하는 기능인 IntersectionObserver를 사용하여 코드를 작성했다.

  • IntersectionObserver를 사용한 화면

  • IntersectionObserver를 사용하지 않은 화면

두 화면의 차이점은 내가 처음 페이지에 접근하였을 때는 동일하게 애니메이션이 발생하는데 다른 화면에 넘어갔다 돌아오면 애니메이션이 다시 재생되도록 한 부분이다. 이런 식으로 화면을 이동할 때 IntersectionObserveDOM노드를 관찰하여 애니메이션을 다시 재생하도록 할 수 있을 것이다.

참조 값을 유지

리액트 컴포넌트 함수의 경우 내부의 State가 변할 때마다 리액트 컴포넌트 함수가 호출되어 화면이 갱신된다. 하지만 리렌더링으로 인해 함수 내의 값이 초기화가 된다. 가끔은 리렌더링이 되어도 기존에 참조하고 있던 컴포넌트 함수 내의 값이 그대로 보존되어야 할 때가 있다. 이런 경우에 useRefcurrent 속성을 이용하여 처리한다.

const ref = useRef(0);

console.log(ref);
// { current: 0 } -> ref의 반환 값

useRef.current속성을 통해 참조의 초기 값에 액세스 할 수 있으며 이 값은 변경이 가능하고, 읽고 사용할 수 있다.

그럼 이 useRef를 사용하여 참조 값을 유지하는 방법을 알아보기 전에 어떤 문제가 있기에 useRef를 사용하여야 하는지 문제의 코드를 보고 넘어가자.

function ManualCounter() {
  const [count, setCount] = useState(0);
  let intervalId;

  const startCounter = () => {
    // 💥 매번 새로운 값 할당
    intervalId = setInterval(() => setCount((count) => count + 1), 1000);
  };
  const stopCounter = () => {
    clearInterval(intervalId);
  };

  return (
    <>
      <p>자동 카운트: {count}</p>
      <button onClick={startCounter}>시작</button>
      <button onClick={stopCounter}>정지</button>
    </>
  );
}

이 코드의 문제는 interval을 위해 선언 된 intervalId 변수이다. 이 변수를 startCounter()함수와 stopCounter()함수에 공유해야 하기에 함수 밖에서 선언해야 한다. 이렇게 되면 count의 상태 값이 바뀔 때마다 컴포넌트 함수가 호출되어 리렌더링 되고, intervalId의 값도 매번 변경될 것이다. 이렇게 되면 브라우저 메모리에서 정리하지 못한 intervalId가 쌓이게된다.

useRef 사용하기

function ManualCounter() {
  const [count, setCount] = useState(0);
  const intervalId = useRef(null);

  const startCounter = () => {
    intervalId.current = setInterval(
      () => setCount((count) => count + 1),
      1000
    );
  };

  const stopCounter = () => {
    clearInterval(intervalId.current);
  };

  return (
    <>
      <p>자동 카운트: {count}</p>
      <button onClick={startCounter}>시작</button>
      <button onClick={stopCounter}>정지</button>
    </>
  );
}

useRef를 사용하여 코드를 개선해보았다. current 속성은 값을 변경해도 상태를 변경할 때 처럼 리액트 컴포넌트가 다시 렌더링되지 않는다. 또한 리액트 컴포넌트가 리렌더링 될 때에도 마찬가지로 current 속성의 값은 참조 값을 유지하고 있다.

이 코드를 실행하여 시작, 정지를 눌러보면 카운트가 1초마다 올라가서 count가 증가하는데, 정지와 시작이 눌릴 때 값은 마지막에 참조하고 있는 값으로 유지가 되는 것을 확인할 수 있다!

끝으로..

지금까지 useRef에 대해 알아보았는데 나는 주로 DOM노드를 조작하기 위해 사용했었는데 참조 값을 유지하는 기능은 이번에 알게되었다.
자주 사용하는 hook은 아니겠지만 그래도 이렇게 한 번 알아두면 추후에 필요할 때 번뜩 생각나서 사용하지 않을까 생각한다.

.
.
.
.
.
.

참고사이트
[daleseo] - useRef 사용법
[React 공식 문서] - Ref로 DOM 조작
[React 공식 문서] - Ref로 값 참조

profile
습관을 들이도록 노력하자!

0개의 댓글