컴포넌트는 자신의 상태값이 변경되거나, 부모로 부터 받은 인자값이 변경되었을 때 새로 렌더링된다. 심지어 useMemo나 useCallback, React.memo 등 최적화함수가 적소에 사용되지 않은 경우 부모로부터 받은 prop이 변경되지 않아도 새로 렌더링되거나 기존의 함수를 새로운 함수로 인식하기 마련이다. 이런 최적화함수에 useRef를 살포시 끼워넣을수 있겠다는 생각을 정리해봤다.
useRef는 일반적으로 특정 DOM을 지정하여 해당 돔의 속성값을 파악하거나 속성값을 변경시키는 용도로 사용했었는데, useState처럼 컴포넌트 내의 변수값을 조회, 수정하는 방법으로도 사용할 수 있다. 이런 상황에서 useState와 useRef의 사용을 비교해보면 렌더링에서의 차이점을 보인다.
*참고)리액트 공식문서의 내용.
useRef() Hook은 DOM ref만을 위한 것이 아닙니다. 본질적으로 useRef는 .current 프로퍼티에 변경 가능한 값을 담고 있는 “상자”와 같습니다. 만약
<div ref={myRef} />
를 사용하여 React로 ref 객체를 전달한다면, React는 모드가 변경될 때마다 변경된 DOM 노드에 그것의 .current 프로퍼티를 설정할 것입니다. useRef는 내용이 변경될 때 그것을 알려주지는 않는다는 것을 유념하세요. .current 프로퍼티를 변형하는 것이 리렌더링을 발생시키지는 않습니다.
의미없는 컴포넌트이긴한데,
이메일을 입력하고 회원가입버튼을 누르면 입력한 이메일이 결과에 뜨게되는 상황이다.
useState를 사용해서 입력 이벤트가 들어올때마다 setState로 입력값을 설정해준다.
마지막에 최종결과를 setResult로 한번 더 확정해준다.
(사실상 불필요한 과정이긴 하다, 입력==결과이므로)
function SignUp() {
const [mail, setMail] = useState<string>('');
const [result, setResult] = useState<string>('');
console.log('render');
const onSubmit = useCallback((e) => {
e.preventDefault();
setResult(result);
}, []);
return (
<div id="container">
<Form onSubmit={onSubmit}>
<Label id="email-label">
<span>이메일 주소</span>
<div>
<Input type="email" id="email" name="email" value={mail} onChange={(e) => setMail(e.target.value)} />
</div>
</Label>
<span>결과: {result}</span>
<Button type="submit">회원가입</Button>
</Form>
</div>
);
}
export default SignUp;
렌더링이 수차례 발생했다.
1번, 화면 첫 렌더링시
~21번, 입력창에 이메일 주소를 입력할 때마다 render발생!
마지막, 회원가입버튼을 눌러 setResult로 새로 상태값이 변경된 경우
기존의 input에서 사용한 value, onChange를 삭제하고 useRef를 input에 지정해준다.
회원가입 버튼을 누르면 입력한 값을 결과에 출력해주는데 이때 한번 setState를 거치게 된다.
콘솔로 찍은 render가 얼마나 호출될까?
function SignUp() {
const mailRef = useRef<HTMLInputElement | null>(null);
const [mail, setMail] = useState<string | null>('');
console.log('render');
const onSubmit = useCallback((e) => {
e.preventDefault();
const { current } = mailRef;
if (!current) return;
else setMail(current.value);
}, []);
return (
<div id="container">
<Form onSubmit={onSubmit}>
<Label id="email-label">
<span>이메일 주소</span>
<div>
<Input type="email" id="email" name="email" ref={mailRef} />
</div>
</Label>
<span>결과: {mail}</span>
<Button type="submit">회원가입</Button>
</Form>
</div>
);
}
export default SignUp;
render는 2번 실행되었다.
1번은 맨 처음 화면이 렌더링되었을때,
2번은 회원가입 버튼을 눌러 setEmail로 상태가 변경되어 리렌더링 되었을 때 실행되었다.
아까 useState로 input값을 수정할때와 달리 render가 적게 일어났다.
결론적으로, useState와 useRef의 가장 큰 차이점은 렌더링이다.
useState는 상태값이 변경될때마다 새로 화면을 불러온다. 하지만 연관검색어 입력과 같은 좀 더 다이나믹한(?) 동적인 뷰가 필요할 땐 useState를 사용할 수 밖에 없다. 이런 상황이 아니라면 useRef로 변수값을 관리하는 것도 좋은 방법인 것 같다.
onChange를 쓰지않고 회원가입 클릭시 setState 한번만 하게 설정한다면 렌더링 차이가 없는데 이부분에 대해서는 어떻게 생각하시나요