이전에 react-hook-form의 input 입력에서는 ref를 사용하는 것을 확인했다.
그렇다면 라이브러리에서도 ref를 사용하는데, useState보다 ref가 더 좋은 방법일까?
useRef와 useState의 차이
React 공식 문서에서는 ref를 "컴포넌트가 일부 정보를 기억하고 싶지만, 해당 정보가 렌더링 로직에 영향을 미치지 않는 경우" 사용하는 것이 적절하다고 설명한다. 주로 포커스 제어, 값 가져오기, DOM 요소 조작 등에 사용된다고도 적혀있다.
반면, useState는 컴포넌트의 상태를 저장하고, 값이 변경될 때 리렌더링을 발생시키는 역할을 한다.
코드 예제: useRef와 useState 비교
import { useState, useRef } from 'react';
export default function Chat() {
const [text, setText] = useState('');
const textRef = useRef(text);
function handleChange(e) {
setText(e.target.value);
textRef.current = e.target.value;
}
function handleSend() {
setTimeout(() => {
alert('Sending: ' + textRef.current);
}, 3000);
}
return (
<>
<input
value={text}
onChange={handleChange}
/>
<button onClick={handleSend}>Send</button>
</>
);
}
React에서 제공한 예제에서는 useState를 사용하여 input 값을 관리하고 있지만, useRef를 활용하여 최신 값을 유지할 수 있도록 했다.
그럼에도 useRef가 input 값을 변경&활용 할 수 있는 이유?
useRef는 DOM 요소에 직접 접근할 수 있기 때문이다. 이를 통해 input 값을 가져오거나 변경할 수 있다.
단계별 과정
- Ref 생성 → useRef 훅을 사용하여 ref를 생성한다.
- Ref를 input 요소에 연결 → JSX에서 ref 속성을 이용해 input에 연결한다.
- Ref를 통해 값 접근 → current.value를 사용하여 input의 값을 읽는다.
코드 예제: useRef를 활용한 input 값 접근
import { useRef } from "react";
function InputRefExample() {
const inputRef = useRef(null);
const handleClick = () => {
alert(`Input value: ${inputRef.current.value}`);
};
return (
<div>
<input ref={inputRef} type="text" placeholder="Type something..." />
<button onClick={handleClick}>Show Input Value</button>
</div>
);
}

기본적인 HTMP에서는 input에 값을 변경해주기 위해 onChange를 사용하지 않는다.
값을 활용하기 위해서는, name에 해당하는 input의 값을 가지고 온다.
useState의 단점
useState를 사용하여 input 값을 관리할 때, 리렌더링 과다, 비동기 업데이트 문제 등의 단점이 있다.
1. 불필요한 리렌더링 방지 (최적화)
🔹 useCallback을 활용한 핸들러 재생성 방지
useCallback을 사용하면 매 렌더링마다 새롭게 생성되는 함수를 방지할 수 있다.
const handleChange = useCallback((e) => {
setText(e.target.value);
}, []);
하지만, useCallback은 단순히 함수를 캐싱할 뿐이며, 컴포넌트 자체의 리렌더링을 막지는 않는다.
🔹 React.memo를 활용한 자식 컴포넌트 리렌더링 방지
React.memo를 사용하면 부모가 리렌더링되더라도 프롭이 변경되지 않는 한 자식 컴포넌트의 리렌더링을 막을 수 있다.
const ChildComponent = React.memo(({ text }) => {
return <p>{text}</p>;
});
하지만, React.memo는 객체나 함수가 props로 전달되면 여전히 불필요한 리렌더링이 발생할 수 있다.
2. useState의 비동기 업데이트 문제
useState는 비동기적으로 상태를 업데이트하므로, 값이 즉시 반영되지 않을 수도 있다.
const [value, setValue] = useState("");
const handleChange = (e) => {
setValue(e.target.value);
console.log(value); // 이전 값이 출력될 수 있음 (비동기 업데이트)
};
function handleSend() {
setTimeout(() => {
alert('Sending: ' + value); // 3초 이내의 변화를 감지하지 못함.
}, 3000);
}
useState가 필요한 경우
하지만, useState에서만 가능한 것이 있다.
- 즉각적인 필드 유효성 검사
- 조건에 따른 제출 버튼 활성화
예를 들어, 입력 값이 특정 조건을 만족해야 제출 버튼을 활성화하는 경우, useState를 활용하는 것이 적절하다.
const [text, setText] = useState("");
const isValid = text.length > 3;
return (
<>
<input value={text} onChange={(e) => setText(e.target.value)} />
<button disabled={!isValid}>Submit</button>
</>
);
반면, useRef는 렌더링 없이 값을 유지할 수 있지만, 값 변경에 따른 UI 업데이트가 필요할 때는 적합하지 않다.
결론
useStateuseRef
렌더링 발생 여부 | O (값 변경 시 리렌더링) | X (렌더링 없이 값 유지) |
최신 값 유지 | X (비동기 업데이트 문제) | O (current에 즉시 반영) |
DOM 요소 직접 접근 | X | O |
필드 유효성 검사 | O | X |
최적화 필요성 | O (불필요한 리렌더링 발생) | X |
useState와 useRef는 모두 input 값을 관리할 수 있지만, 개발하려는 의도에 따라 선택적으로 활용하는 것이 중요하다.
참고 자료
'FE > React' 카테고리의 다른 글
Tailwind CSS vs Styled Components (0) | 2025.03.02 |
---|---|
useState가 의도대로 동작을 하지 않는다. (0) | 2025.02.22 |
react-hook-form, register 렌더링 분석 (0) | 2025.01.19 |
Recoil 메모리 누수 확인하기 (0) | 2025.01.11 |
Recoil (0) | 2024.12.27 |