이번에는 React Hook 중 하나인 useRef를 이용해 컴포넌트 내부에서 값을 유지하는 방법과 특정 DOM 요소에 접근하는 방법을 알아보겠습니다.
useRef는 Reference 객체를 생성하는 React Hook입니다.
기본적인 사용 방법은 다음과 같습니다.
const refObject = useRef()
초기값을 전달할 수도 있습니다.
const refObject = useRef(0)
1. useRef란?
useRef를 호출하면 current라는 프로퍼티를 가진 Reference 객체가 생성됩니다.
const refObject = useRef(0)
console.log(refObject)
개념적으로 다음과 같은 객체를 생각할 수 있습니다.
{
current: 0
}
따라서 Reference 객체에 저장된 값을 사용하려면 current를 이용합니다.
refObject.current
값을 변경할 수도 있습니다.
refObject.current = 10
2. useRef의 가장 중요한 특징
useRef는 값을 저장할 수 있다는 점에서 useState와 비슷합니다.
하지만 가장 중요한 차이점이 있습니다.
useRef의 current 값이 변경되어도 컴포넌트는 리렌더링되지 않습니다.
예를 들어 다음과 같이 작성할 수 있습니다.
const countRef = useRef(0)
countRef.current += 1
countRef.current의 값은 증가합니다.
하지만 이 값의 변경 자체가 컴포넌트를 다시 렌더링하지는 않습니다.
반면 useState는 State가 변경되면 리렌더링됩니다.
const [count, setCount] = useState(0)
setCount(count + 1)
따라서 두 Hook은 용도가 다릅니다.
UI에 영향을 주는 값
const [count, setCount] = useState(0)
값이 변경되면 화면도 변경되어야 한다면 useState를 사용합니다.
렌더링과 관계없이 유지해야 하는 값
const countRef = useRef(0)
값은 유지해야 하지만 값이 변경되어도 화면을 다시 렌더링할 필요가 없다면 useRef를 사용합니다.
3. useRef의 특징
useRef는 다음과 같은 특징을 가지고 있습니다.
1) 값을 저장할 수 있다
const refObject = useRef(0)
2) 값은 current에 저장된다
refObject.current
3) 값이 변경되어도 리렌더링되지 않는다
refObject.current += 1
4) 컴포넌트가 리렌더링되어도 값이 유지된다
일반 변수는 컴포넌트 함수가 다시 실행될 때 다시 초기화됩니다.
하지만 useRef로 생성한 객체의 값은 리렌더링 이후에도 유지됩니다.
4. 사용 사례 1 - 렌더링 횟수와 관계없이 값 유지하기
useRef의 가장 대표적인 사용 방법 중 하나는 컴포넌트 내부에서 값을 유지하는 것입니다.
예를 들어 다음과 같이 작성할 수 있습니다.
const Register = () => {
const countRef = useRef(0)
const onChange = () => {
countRef.current++
console.log(countRef.current)
}
return (
<div>
<textarea
name="bio"
value={input.bio}
onChange={onChange}
placeholder="자기소개"
/>
{input.bio}
</div>
)
}
textarea에 값을 입력할 때마다 onChange가 실행됩니다.
countRef.current++
따라서 콘솔에는 다음과 같이 값이 출력됩니다.
1
2
3
4
5
...
일반 변수로 만들면 어떻게 될까?
useRef 대신 일반 변수를 사용해 보겠습니다.
const Register = () => {
let count = 0
const onChange = () => {
count++
console.log(count)
}
// ...
}
문제는 State가 변경되면 컴포넌트가 다시 렌더링된다는 것입니다.
컴포넌트가 리렌더링되면 Register 함수가 다시 실행됩니다.
그러면 다음 코드도 다시 실행됩니다.
let count = 0
따라서 값이 다시 0으로 초기화됩니다.
결과적으로 값을 증가시키더라도 다음과 같이 계속 1이 출력될 수 있습니다.
1
1
1
1
1
useRef를 사용하면?
const countRef = useRef(0)
useRef의 값은 컴포넌트가 리렌더링되어도 유지됩니다.
따라서 값을 증가시키면 다음과 같이 계속 누적할 수 있습니다.
1
2
3
4
5
값을 변경할 때는 current를 사용합니다.
countRef.current++
5. 컴포넌트 외부에 변수를 선언하면 안 될까?
다음과 같이 컴포넌트 외부에 변수를 선언할 수도 있습니다.
let count = 0
const Register = () => {
// ...
}
이 경우 리렌더링되어도 count가 초기화되지 않을 수 있습니다.
하지만 이 변수는 특정 Register 컴포넌트만을 위한 값이 아닙니다.
여러 컴포넌트가 동일한 변수를 공유할 수 있습니다.
<Register />
<Register />
각 컴포넌트가 독립적인 값을 관리해야 하는 상황에서는 문제가 발생할 수 있습니다.
따라서 컴포넌트별로 독립적으로 값을 유지해야 한다면 useRef를 사용하는 것이 적합합니다.
정리하면 다음과 같습니다.
방법리렌더링 후 값 유지컴포넌트별 독립적인 값
| 컴포넌트 내부 일반 변수 | ❌ | ✅ |
| 컴포넌트 외부 일반 변수 | ✅ | ❌ |
| useRef | ✅ | ✅ |
6. 사용 사례 2 - 특정 DOM 요소에 focus 주기
useRef의 또 다른 중요한 기능은 특정 DOM 요소에 직접 접근하는 것입니다.
예를 들어 이름을 입력하지 않고 제출 버튼을 클릭했을 때 이름 입력창에 자동으로 포커스를 주고 싶다고 가정해 보겠습니다.
먼저 Reference 객체를 생성합니다.
const inputRef = useRef()
그리고 접근하려는 input 요소의 ref 속성에 연결합니다.
<input ref={inputRef} />
이제 inputRef.current를 통해 실제 DOM 요소에 접근할 수 있습니다.
inputRef.current
전체 예제
const Register = () => {
const inputRef = useRef()
const onSubmit = () => {
if (input.name === '') {
inputRef.current.focus()
}
}
return (
<div>
<input
ref={inputRef}
name="name"
value={input.name}
onChange={onChange}
placeholder="이름"
/>
{input.name}
<button onClick={onSubmit}>제출</button>
</div>
)
}
여기서 다음 코드가 중요합니다.
ref={inputRef}
inputRef와 실제 input DOM 요소를 연결합니다.
React가 화면을 렌더링한 이후에는 inputRef.current를 통해 실제 input 요소에 접근할 수 있습니다.
inputRef.current
따라서 다음과 같이 focus() 메서드를 호출할 수 있습니다.
inputRef.current.focus()
사용자가 이름을 입력하지 않은 상태에서 제출 버튼을 클릭하면 해당 input에 자동으로 포커스가 이동합니다.
7. useRef로 DOM에 접근하는 과정
useRef를 이용해 DOM 요소에 접근하는 과정은 다음과 같습니다.
1. Reference 객체 생성
const inputRef = useRef()
2. DOM 요소의 ref 속성에 연결
<input ref={inputRef} />
3. current를 통해 실제 DOM 요소에 접근
inputRef.current
4. DOM 메서드 사용
inputRef.current.focus()
이 방식을 이용하면 특정 요소에 focus를 주는 등의 작업을 수행할 수 있습니다.
8. useState와 useRef 비교
두 Hook의 차이를 다시 정리하면 다음과 같습니다.
| 구분 | useState | useRef |
| 값 저장 | 가능 | 가능 |
| 값 변경 시 리렌더링 | 발생 | 발생하지 않음 |
| 리렌더링 후 값 유지 | 유지 | 유지 |
| UI에 영향을 주는 값 | 적합 | 부적합 |
| 렌더링과 관계없는 값 | 가능하지만 불필요한 리렌더링 가능 | 적합 |
| DOM 요소 접근 | 직접적인 용도 아님 | 가능 |
예를 들어 화면에 표시되는 카운트라면 useState가 적합합니다.
const [count, setCount] = useState(0)
반면 렌더링 횟수처럼 화면에 반영할 필요는 없지만 값을 유지해야 한다면 useRef가 적합합니다.
const countRef = useRef(0)
마무리
이번에는 React Hook 중 하나인 useRef에 대해 알아봤습니다.
useRef의 가장 중요한 특징은 다음과 같습니다.
값은 리렌더링 이후에도 유지되지만, 값이 변경되었다고 해서 컴포넌트를 다시 렌더링하지는 않는다.
따라서 useRef는 다음과 같은 경우에 사용할 수 있습니다.
- 렌더링과 관계없이 값을 유지할 때
- 렌더링 횟수 등을 저장할 때
- 특정 DOM 요소에 접근할 때
- input 요소에 focus를 줄 때
기본적인 사용 방법은 다음과 같습니다.
const refObject = useRef(초기값)
실제 값은 current를 통해 사용합니다.
refObject.current
값이 변경되면 UI도 변경되어야 한다면 useState를 사용하고,
const [count, setCount] = useState(0)
값은 유지해야 하지만 화면을 다시 렌더링할 필요가 없다면 useRef를 사용하면 됩니다.
const countRef = useRef(0)
즉, useRef는 컴포넌트의 리렌더링과 관계없이 값을 유지하거나 특정 DOM 요소에 직접 접근하고 싶을 때 사용하는 React Hook이라고 이해하면 됩니다.