useRef 올바르게 사용하기


useRef

💡 useRef란?

우리는 바닐라 자바스크립트에서 querySelector, getElementById 등을 이용해 DOM을 직접적으로 선택하여 활용하곤 했었다.

하지만 리액트에서는 직접 DOM에 접근하여 조작하는 것을 권장하지 않기에 우리는 Ref를 활용해야 한다.

즉, 우리는 useRef라는 내장 hooks를 통해 DOM에 접근할 수 있고, 이를 리액트 컴포넌트 내에서 활용할 수 있는 것이다!

하지만 useRef는 단순히 DOM에 접근하기 위해서만 사용되는 것은 아니다. 데이터를 담는 저장소로써의 역할도 하는데, 데이터 저장소로써의 역할에 대해 먼저 이해한 뒤 DOM 접근 용도에 대해서 알아보도록 하자.

우선 간단하게 useRef의 개괄적인 코드 형태부터 알아보자.

1
2
3
4
5
6
7
import { useRef } from 'react';

export const Count = () => {
const countRef = useRef();
console.log(countRef.current);
return <div>Count!</div>;
};

이게 가장 기본적인 useRef의 코드 구조다. 하지만 useRef를 처음 접해보는 사람들은 중간에 쓰인 current가 도대체 뭔지 의문부터 들었을 것이라 생각된다.


ref.current?

current는 쉽게 말해 데이터를 담는 상자(오브젝트)라고 볼 수 있다.
즉, 위에서 말했듯이 ref가 꼭 DOM에만 접근하기 위한 것은 아니라는 뜻이다.

ref.current는 참조값에 접근하여 업데이트된 새로운 값을 current에 담는다. 위의 코드 예시를 예로 들자면, div라는 참조값에 뭔가 변화가 생기면 (물론 div가 변할 일이 뭐가 있겠냐만은…) 우리의 ref.current는 이를 감지하고 새로운 값을 담아내어 업데이트 하는 것이다.

조금 더 쉽고 직관적인 방식으로 예를 들어보면,

1
2
3
4
5
6
7
8
9
10
import { useRef } from 'react';

export const Count = () => {
// 우리는 이와 같이 count 횟수에 대한 값을 useRef.current에 담아 사용할 수 있는 것이다.
const countRef = useRef(0);
const handleClick = () => {
countRef.current += 1;
};
return <button onClick={handleClick}>클릭해주세요</button>;
};

근데… 그럼 이게 state와 다른 점이 뭘까? 아래에서 더 자세히 알아보도록 하자.


🔮 useRef의 특징 (중요)

아래의 두 룰은 반드시 숙지해야 하는 useRef의 핵심 개념이다.

ref의 특징

  1. 참조값이 변해도 리렌더링을 야기하지 않는다.
  2. 컴포넌트가 리렌더링 되더라도 참조값은 유실되지 않는다.

글만 봐서는 감이 잡히지 않을 수도 있는데, 위에서 활용했던 Count 컴포넌트의 예시를 활용해 더 자세히 설명해보도록 하겠다.

1
2
3
4
5
6
7
8
9
10
11
12
13
import { useRef } from 'react';

// useRef() 안에 들어가 있는 값은 초기값이다. (default value)

export const Count = () => {
const countRef = useRef(0);
const handleClick = () => {
countRef.current += 1;
console.log(`저는 ${countRef.current}번 클릭되었습니다!`);
};
console.log('저는 렌더링 될 때마다 출력됩니다!');
return <button onClick={handleClick}>클릭해주세요</button>;
};

위의 코드에서 만약 count 횟수를 담는데 state를 사용하고, setState()를 통해 count값을 업데이트 해줬다면 어땠을까?

state의 변화는 컴포넌트의 리렌더링을 야기하기 때문에 count 횟수가 변동될 때마다 컴포넌트가 리렌더링 되었을 것이다. 하지만 위의 useRef의 예시에서는 count 횟수가 증가해도 컴포넌트가 리렌더링 되지 않는 것을 확인할 수 있다.


🎲 DOM에 접근하기

이제 DOM에 접근하기 위한 용도로써의 useRef에 대해 알아보도록 하자.

useRef를 통해 특정 DOM에 접근하여 참조하는 것은 아래의 코드 방식을 따른다.

1
2
3
4
5
6
7
8
9
10
11
import { useRef, useEffect } from 'react';

function ToDom() {
const domRef = useRef();
useEffect(() => {
const divElement = elementRef.current;
// // <div>나를 참조해주세요</div>가 출력될 것이다.
console.log(divElement);
}, []);
return <div ref={domRef}>나를 참조해주세요</div>;
}

위의 코드를 글로써 다시 정리하자면…

  1. 우선, const something = useRef();와 같이 참조값을 정의하고
  2. 그 참조값을 DOM에 담아준다. <div ref={something}> </div>
  3. 그리고 컴포넌트가 마운트 되면 우리의 something이라는 refdiv DOM을 가리키게 된다.

타입스크립트에서의 방법 (추가)

TODO 앱을 만들다가 input의 자동 포커싱 기능을 구현하려 했는데, 타입스크립트에서의 useRef방식에는 몇 가지 룰이 존재했다… 따라서 이를 추가적으로 기록해보고자 한다.

내가 간과했던 것은 초기 렌더링 시에는 참조값이 null이기에, useEffect 내에서 current가 비어있는지 여부를 우선적으로 체크해줘야 했는데 이를 빼먹었다. 아래의 코드를 봐보자!

1
2
3
4
5
6
7
8
9
10
11
12
13
import { useRef, useEffect } from 'react';

function ToDom() {
// HTMLInputElement 타입을 지정해주고, 초기값을 null로 설정해준다.
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
// 이렇게 inputRef.current가 비어있는지 아닌지를 체크해야 오류가 나지 않는다. (초기 렌더링 시에는 current가 null로 비어있음)
if (domRef.current) {
inputRef.current.focus();
}
}, []);
return <input type='text' ref={domRef} />;
}

마무리

여기까지가 useRef의 기본 중의 기본이다.
참고로 첨언하자면, 참조값을 변화시키고 싶을 때는 함수형 컴포넌트 내에서 바로 변화를 주는 것이 아니라, useEffect나 핸들링 함수 내에서 변화를 주는 것이 좋은 방식이라고 한다!

아직은 너무 기본적인 것들 뿐이고 부족한 것이 많지만… 기본적인 hooks들에 대한 공부를 우선적으로 마치고, 더 깊은 개념들과 활용법들에 대해 공부해볼 계획이다.

포스팅에 참조한 자료

The Complete Guide to useRef() and Refs in React


useRef 올바르게 사용하기

https://hoonjoo-park.github.io/react/4.useRef/

Author

Hoonjoo

Posted on

2022-02-04

Updated on

2022-02-07

Licensed under

Comments