
❓
useRef를 사용하면 다시 렌더링이 되지 않는다는데
❓ 왜 다시 렌더링이 안될까요 ??
❓ 어떤 원리로 다시 렌더링이 안될까요 ??
❓useState와 어떤 차이점이 있길래 안될까요 ??
: useRef는 리액트 훅의 한 종류로, Ref는 reference(참조)의 줄임말이다
⬇️ 리엑트 공식문서에서의 정의
useRef는 렌더링에 필요하지 않은 값을 참조할 수 있는 React Hook이다.
⇒ 컴포넌트가 특정 정보를 기억하도록 하고 싶지만 해당 정보가 새 렌더링을 촉발하지 않도록 하려는 경우 ref를 사용할 수 있다.
useRef를 이용하면 ? (장점)
컴포넌트의 최상위 레벨에서 useRef를 호출하여 ref를 선언한다.
import { useRef } from 'react';
function MyComponent() {
const intervalRef = useRef(0); //initialValue에 초기값 넣어주면 된다.
const inputRef = useRef(null);
// ...
매개변수
initialValue: ref 객체의 current프로퍼티 초기 설정값이다.
여기에는 어떤 유형의 값이든 지정할 수 있다. 이 인자는 초기 렌더링 이후부터는 무시된다 !
반환 값

useRef는 단일 프로퍼티를 가진 객체를 반환한다 !
current: 처음에는 전달한 initialValue로 설정되고 나중에 다른 값으로 바꿀 수 있다ref어트리뷰트로 React에 전달하면 React는 current프로퍼티를 설정합니다.주의사항
ref.current 프로퍼티(props)는 state와 달리 변경을 직접 할 수 있다.
하지만 렌더링에 사용되는 객체(예: state의 일부)를 포함하는 경우 해당 객체를 변이해서는 안 된다 (UI를 그리는 데 쓰이는 객체는 직접 바꾸면 X)
⇒ UI 업데이트에 영향을 주는 데이터는 state로, 내부에서만 쓰는 값은 ref로 써야 O
ref.current 프로퍼티를 변경해도 React는 컴포넌트를 다시 렌더링하지 않는다.
ref는 일반 JavaScript 객체이기 때문에 React는 사용자가 언제 변경했는지 알지 못한다
초기화를 제외하고는 렌더링 중에 ref.current를 쓰거나 읽지 말자.
이렇게 하면 컴포넌트의 동작을 예측할 수 없게 된다.
출처 ⇒ 리액트 공식 문서
: 내부의 변수들이 담고 있는 기존에 저장한 값들이 초기화되고, 함수 로직이 재실행됨을 의미
React에서 상태(state)의 변경을 감지하면 자동으로 컴포넌트가 렌더링이 된다 !


하지만 위의 사진 같이 상태를 변경하는 것이 아닌 그저 값만 변경 했을 때에는
리엑트에서 감지 하지 않기 때문에 UI를 다시 그리지 않는다
⇒ 렌더링은 최초 한 번만 실행됨 ⇒ 따라서 처음 값인 0만 UI에 출력
렌더링이 될 때


위와 같이 useState를 사용해 상태(state)를 변경해주니
리렌더링이 일어나 UI가 계속해서 업데이트 되는 것을 볼 수 있다 !
콘솔 밀림 현상
import { useState } from "react"; import "./App.css"; function App() { const [count, setCount] = useState(0); const clickCount = () => { setCount((prev) => prev + 1); console.log(count); }; return ( <div> <p>{count}</p> <button onClick={clickCount}>클릭</button> </div> ); } export default App;이 코드를 실행시키면
이런식으로 UI에는 6이 찍히는데 console에는 5가 찍힌다.
왜 이렇게 콘솔 밀림 현상이 생기는 걸까?! 라는 궁금증에
한번 알아보았다 !
먼저 useState의setState는 (위의 코드애서는setCount) 비동기적으로 작동한다 !setCount((prev) => prev + 1); console.log(count); // 여기서 찍히는 count는 > 아직 "업데이트 전" 값여기에서 setCount를 호출해도 바로 count 값이 변경되지 않는다 ..
React는 상태 업데이트를 비동기적으로 처리해서 렌더링을 <최적 타이밍에 한 번에> 수행하려고 한다.
그래서
console.log(count)는 이전 렌더링의count값을 출력흐름을 정리해서 말해보자면
1. 버튼을 클릭해서 clickCount 함수 실행
2. React가 상태 변경 예약 → 이후 렌더링 스케쥴링
3. 렌더링 완료되면 count 값이 새로 반영됨
여기서 상태와 값을 혼동할 수 있는데 밑에 간단하게 표로 정리했다
| 용어 | 설명 |
|---|---|
| 값 (value) | 변수에 저장된 |
| 일반적인 값 | |
| 상태 (state) | React가 추적하고 관리하는 값 |
| 항목 | 일반 값 (let, const, var) | 상태 (useState, this.setState) |
|---|---|---|
| 누가 관리 ? | 개발자가 직접 | React가 |
| 내부적으로 관리 | ||
| 값이 바뀌면 ? | 그냥 메모리만 바뀜 | React가 감지해서 리렌더링 발생 |
| React 추적 | 감지 X | 감지해서 |
| 화면 업데이트 |
여기서 잠깐 ! 리렌더링이 언제 일어나는지도 알아보자 !!
- 자신의 state가 변경될 때
- 부모 컴포넌트에서 전달받은 props가 변경될 때
- 부모 컴포넌트가 리렌더링될 때 (자식에게 변경된 props가 없어도)
사실 리렌더링은 React 컴포넌트가 화면에 변경 사항을 반영하기 위해 반드시 필요한 과정이다.
그러나 모든 리렌더링이 의미 있는 업데이트로 이어지는 것은 아니기 때문에
불필요한 리렌더링을 줄이는 최적화가 중요하다
불필요한 렌더링은 피해야 하는데 ..
상태(state)를 변경하면 자동으로 컴포넌트가 렌더링되는데
함수형 컴포넌트는 함수 그 자체이기 때문에 리렌더링이 일어나게 되면 함수가 다시 불러와져서
내부에 있는 모든 변수들이 초기화가 된다.
⇒ 따라서 원하지 않는 렌더링 때문에 곤란해질 때가 있다
그래서 useRef를 언제 사용하는거야 ?
useRef는 위와 같이 다시 렌더링하지 않고 변경 가능한 값을 저장하고 싶을 때 사용한다 !예를 들어, DOM 요소에 직접 접근하거나 이전 값을 기억하거나 렌더링과 > 상관없는 내부 상태를 저장할 때 유용하다
이런 특성 덕분에
useRef는 불필요한 리렌더링을 피하면서도 값은 유지할 수 있는 훌륭한 도구가 된다.
앞선 위의 내용을 이해했다면 useRef는 충분히 이해할 수 있다

먼저 useRef를 할당한 ref를 콘솔로 출력해보면

이렇게 객체의 current라는 프로퍼티(props)를 통해 ref의 current 값에 접근할 수 있다.
import { useRef } from 'react';
export default function Counter() {
let ref = useRef(0);
function handleClick() {
ref.current = ref.current + 1;
alert('You clicked ' + ref.current + ' times!');
}
return (
<button onClick={handleClick}>
Click me!
</button>
);
}
이것은 값 자체를 변경한다 !! ➡️ ref.current = ref.current + 1;
useRef는 위에 작성한 예시처럼 ref.current = ... 처럼 읽고 쓰는 것이 자유롭고 의도적으로 값을 변경할 수 있다. 리액트가 추적하지 않는다 !
const myRef = useRef("hi");
// myRef = { current: "hi" } => 객체의 형태로 출력됨
앞에서 언급한 것처럼 useRef로 만든 값은 단순한 일반 자바스크립트 객체이다.
따라서 React는 이 객체 안에서 어떤 일이 일어나는지 관찰하지 않기 때문에
리액트 사용자가 언제 변경했는지 모른다 !
이것이 useRef가 값이 변경되어도 리렌더링 되지 않는 이유 !!
즉 useRef는 상태를 변경하는 것이 아닌 값을 보관하고 변경하는 것이기 때문에 react는 이 컴포넌트를 다시 그려야겠다는 판단을 하지 X
다시 흐름을 정리를 하자면
- useRef는 상태를 변경하는 것이 아닌 객체 안의 값을 변경
- useRef는 일반 JS 객체이므로 React가 변경을 감지하지 못해 리렌더링 X
- 값은 유지되지만 렌더링에 반영 X ⇒ UI에 반영 X
그렇다면 react는 어떻게 상태(state) 변경만 인지하고 리렌더링을 하고 직접 한 값 변경은 인지를 못하는 걸까?
위에서 봤던 것처럼 useRef()는 일반적익 JS(자바스크릡트) 객체이다
일반적인 프로그래밍 언어와 같이 자바스크립트 역시 heap 영역과 stack 영역에서 메모리를 관리한다.
heap 영역과 stack 영역에서 메모리가 어떻게 관리되는지 간단하게 알아보자
자바스크립트에서 변수들의 메모리 위치는?
참조형 데이터는 힙에 저장주소만 스택에 저장즉, useRef를 통해 저장된 값들은 heap 영역에 저장된다 !
useRef가 리렌더링이 일어나지 않는 이유 단계적으로 설명하자면
1️⃣
useRef는heap영역에 저장 !
2️⃣ heap 영역에 있기때문에 어플리케이션 종료까지같은 메모리 값을 유지
3️⃣ 그러다 보니 useRef로 관리하는 값이 변경되어도동일 메모리 주소를 유지
4️⃣ 얕은 비교를 하는 React의 리렌더링 감지에서는 '===' 연산이 항상 true를 반환
5️⃣ 변경사항 감지가 되지 않아리렌더링 X
- 입력창에 focus 주기 (DOM 접근)
React에서 컴포넌트가 마운트되자마자 특정 입력창에 자동으로 focus를 주고 싶을 때 useRef를 사용하면 DOM에 직접 접근
import { useEffect, useRef } from "react";
function App() {
const idInputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
idInputRef.current?.focus(); // 컴포넌트 마운트 시 포커스
}, []);
return (
<div>
<input ref={idInputRef} placeholder="아이디" />
</div>
);
};
- 로그인 할 때 아이디 값 관리하기
로그인 시 아이디 값을 임시로 저장만 하고 싶고 화면에 보여줄 필요는 없을 때 useState보다 useRef가 적절 !
import { useRef } from "react";
const LoginForm = () => {
const idRef = useRef("");
const handleChange = (e) => {
idRef.current = e.target.value;
};
const handleLogin = () => {
console.log("입력된 아이디:", idRef.current);
};
return (
<div>
<input placeholder="아이디" onChange={handleChange} />
<button onClick={handleLogin}>로그인</button>
</div>
);
};
재밌는 사실 !
사실 useRef는 React에서 내부적으로 useState를 통해서 구현될 수 있다는 사실 !!
function useRef(initialValue) { const [ref, _] = useState({ current: initialValue }); return ref; }초기 렌더링 시
useState로{ current: initialValue }객체를 생성하고그 객체를 그대로 반환한다.
이 객체는 렌더링이 반복되어도 항상 동일한 참조값을 반환하기 때문에
컴포넌트가 리렌더링되어도 객체 내부 값은 유지 !
왜 setter가 필요 없을까?
useState는 값이 바뀌면 리렌더링을 유도하기 위해setState를 제공한다.하지만
useRef는 반환된 객체를 직접 수정하므로setState와 같은 setter가 필요 없다 !ref.current = "새로운 값";이렇게 값을 직접 바꾸더라도 리렌더링은 발생하지 X !
항상 개발을 할때는 정확한 원리를 이해하고 왜 그렇게 동작하는지를 알아가면서 개발하자 !