useRef를 예전 파이널 프로젝트에 도입한 적이 있었다.
그러나 그당시는 useRef의 특징도 모른채 자식 컴포넌트의 퍼모먼스를 부모 컴포넌트로 제어하기 위해 잠시 코드를 분석해서 급조로 설계만 하였다.
막상 사용하고 나서는 어떤 원리인지는 하나도 모르고 사용하였는데,
이번 시간에는 React Hooks의 useRef에 대해 알아보자.
한마디로 요약하자면 아래와 같다.
Ref는 render 메서드에서 생성된 노드 혹은 React Element에 접근하는 방법이다
즉, Valilla Javascript에서는 DOM 객체에 접근하기 위해 querySelector
나 getElementById
API를 사용해야 하지만, React에서는 DOM API를 중구난방 사용할 경우 디버깅, 코드 해석(리뷰)이 복잡해진다.
그런 단점을 해소하고자 DOM 제어를 ref 라는 속성 기능이 대신 수행 해준다.
또한 저장 공간으로도 자주 활용하기도 한다.
( 별코딩 님의 영상 자료를 보고 각색하여 정리하였습니다. )
state의 변화 -> 렌더링 -> 컴포넌트 내부 변수들 재가동(값들이 변화)
Ref의 변화 -> NO 렌더링 -> 컴포넌트 내부 변수들 미가동(값들이 유지)
State의 변화 -> 렌더링 -> 그래도 Ref의 값은 유지됨
위 요약을 통해 단 번에 알 수 있는 특징은 변경 시키지 말아야 할 값을 저장할 때
매우 유용하다는 것을 알 수가 있다.
그러나 3번째 문단의 그래도 ref의 값은 유지됨
이라는 문구를 이해하는데 큰 어려움이 있었는데, 아래 코드의 예제를 보면 저 뜻이 무엇을 의미하는지 알 수가 있다.
import "./styles.css";
import { createRef, useRef, useState } from "react";
export default function App() {
const [count, setCount] = useState(0);
const countRef = useRef(0);
let countVar = 0;
const increaseCount = () => {
setCount(count + 1);
};
const increaseCountRef = () => {
countRef.current = countRef.current + 1;
};
const increaseVar = () => {
countVar = countVar + 1;
};
const printResult = () => {
console.log(`ref: ${countRef.current}, var: ${countVar}`);
};
return (
<>
<p>Var: {countVar}</p>
<p>Ref: {countRef.current}</p>
<button onClick={increaseCount}>렌더 발생</button>
<button onClick={increaseVar}>Var 올려</button>
<button onClick={increaseCountRef}>Ref 올려</button>
<button onClick={printResult}>Ref Var 올려</button>
</>
);
}
countVar
은 일반 변수, countRef
는 ref 변수, count
state 변수로 구성 되어있다.
여기서 일반 변수 4번 클릭, rev 변수는 2번 클릭하여 Ref Var 올려
버튼을 눌러보자
console.log 결과에 따라 일반 변수의 값은 4, Ref 변수의 값은 2이다.
렌더 발생
버튼을 누르면 state 변수의 값이 증가되며 렌더링이 일어난다.
그 다음 다시 console.log 결과 버튼을 눌러보면 일반 변수의 값은 0, Ref 변수의 값은 그대로 유지하고 있다.
위에 저장공간에 state 변경 시 일어나는 렌더링으로 인해 ref를 설명했던 말인 그래도 Ref의 값은 유지됨
이 위 코드 상황일 때를 의미하는 것이다.
다시 말해서 렌더링이 일어나든 말든 Ref는 항상 본인의 값을 유지하고 Ref 값이 변화가 일어나도 렌더링이 발생하지 않는다.
"Ref 값이 변화가 일어나도 렌더링이 발생하지 않는다." 뜻은 즉, 렌더링이 발생하여도 state 또한 값이 유지는 되지만, Ref는 렌더링이 값도 유지해주면서 렌더링 조차 일어나지 않는다는 차이가 가장 큰 포인트다!!!
import "./styles.css";
import { createRef, useRef, useState } from "react";
// useRef는 dom을 변경할 때 사용
export default function App() {
const [count, setCount] = useState(0);
const countRef = useRef(0);
console.log("렌더링 발생");
const increaseCountState = () => {
setCount(count + 1);
};
const increaseCountRef = () => {
countRef.current = countRef.current + 1;
// console.log(countRef);
};
return (
<>
<p>state: {count}</p>
<p>Ref: {countRef.current}</p>
<button onClick={increaseCountState}>State 올려</button>
<button onClick={increaseCountRef}>Ref 올려</button>
</>
);
}
console.log("렌더링 발생")
이라는 코드는 State의 변화일 경우 매번 렌더링되지만,
Ref의 변화가 일어나도 렌더링이 일어나지 않는다.
여러므로 불필요 렌더링을 관리할 코드를 작성할때 Ref는 유용하게 쓰일거 같다는 생각이 든다.
앞서 언급 했듯 별도의 DOM API(querySelector) 없이 DOM에 접근할 수 있는데,
해당 엘리먼트의 Style 혹은 focus를 통해 이벤트 퍼모먼스를 다룰 수 있다.
이는 React가 갖고 있는 독보적인 큰 무기 중 하나이다.
import "./styles.css";
import { useRef, useState } from "react";
// useRef는 dom을 변경할 때 사용
export default function App() {
const myRef = useRef(null);
console.log('렌더링 발생')
return (
<>
<button
onClick={() => {
console.log(myRef);
console.log(myRef.current);
myRef.current.style.backgroundColor = "red";
}}
>
색변경
</button>
<div className="App">
<div ref={myRef}>박스</div>
</div>
</>
);
}
Button을 클릭하면 div에 있는 background가 빨간색으로 바뀌는데, 여기서 렌더링이 발생되지 않아 console.log의 '렌더링 발생' 문구가 콘솔창에 출력 되지 않는다.
import "./styles.css";
import { useEffect, useRef } from "react";
export default function App() {
const inputRef = useRef();
useEffect(() => {
// console.log(inputRef);
inputRef.current.focus();
}, []);
const alertLog = () => {
alert(`환영합니다. ${inputRef.current.value}`);
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} type="text" placeholder="usename" />
<button onClick={alertLog}>로그인</button>
</div>
);
}
첫 렌더링(componetnDidMount) 과정이 일어날 경우 바로 해당 input에 focus가 맞춰진다.
또한 버튼을 누름과 동시에 input에 작성한 내용이 ref 속성에 저장되어 alert창에 전달 되고,
종료되는 동시에 fucus가 다시 input 태그에 맞춰지게 된다.
useRef
는 사용하면 할 수록 불필요 렌더링을 막아줄 수 있고, DOM API 필요 없이
엘리먼트 혹은 노드를 접근할 수 있어 매우 유용한 React Hooks 이다.