이번 포스트에서는 리액트 훅 시리즈인 useRef
에 대해서 공부한다. 왜냐하면 내일 업무에서 useRef
를 써야 하기 때문이다🥲.. useRef
를 사용해 슬라이더(prev/next 버튼)을 만들어 볼 건데, 무사히 완성되면 관련 내용도 정리해서 포스팅해 볼 계획이다.
useRef
는 리액트에서 DOM을 제어할 수 있는 쿼리셀렉터와 같은 역할을 한다 정도로만 알고 있었는데, 이번 기회에 활용법과 응용 예시를 자세히 공부하면 좋겠다.
useRef
란?함수형 컴포넌트에서 useRef
를 부르면 ref
오브젝트를 반환해준다.
const ref = useRef(value);
ref
오브젝트는 {current: value}
이렇게 생겼다. 우리가 인자로 넣어준 초깃값value
은 ref
안에 있는 current
에 저장이 된다. ref
오브젝트는 수정이 가능하기 때문에 원하는 값으로 바꿀 수 있다.
반환된 ref
는 컴포넌트의 전 생애 주기동안 유지된다. 그 말은, 컴포넌트가 계속해서 렌더링이 되어도 언마운트되기 전까지는 값을 유지한다는 뜻이다.
state
와 비슷하게 무언가를 저장해두는 저장공간으로 사용이 된다. state
를 변경하면 컴포넌트가 자동으로 재렌더링이 된다. 함수형 컴포넌트는 재렌더링이 되면 함수가 다시 불려지는 것이기 때문에, 내부에 있는 모든 변수들이 초기화가 된다. 이러한 함수형 컴포넌트의 특징 때문에 원하지 않는 렌더링이 일어날 때마다 퍼포먼스가 떨어질 가능성이 있고, 변수 안의 값을 유지하기 위한 조치를 취해야 한다.
useRef
를 저장공간으로 사용한다면 ref
안의 값을 변경해도 렌더링이 일어나지 않고 변수들의 값이 유지된다. 마찬가지로, state
가 변경되어 렌더링이 일어나도 ref
의 값은 유지된다. 그렇기 때문에, 변경 시 렌더링을 발생시키지 않아야 하는 경우에 유용하게 사용될 수 있다.
ref
를 통해 DOM 요소에 접근, 조작할 수 있다. 예를 들면 input
요소를 선택하지 않아도 focus를 준다거나 할 때 활용할 수 있다. 바닐라 자바스크립트의 document.querySelector()
역할을 해 준다.
const inputRef = useRef(value);
<input ref={inputRef} />
useRef
변수를 우리가 접근하고자 하는 요소의 ref
속성으로 넣어주기만 하면 된다.
useRef
에 대해 간단히 알아봤으니, 이제 예제를 통해 활용법을 익혀보자.
state
와 ref
의 차이import React, { useState } from 'react';
function App() {
const [count, setCount] = useState(0);
const addCountState = () => {
setCount(count+1);
}
console.log("렌더링...🎨");
return (
<div>
<p>state: {count}</p>
<button onClick={addCountState}>add state</button>
</div>
);
}
export default App;
화면에 count
state를 보여주고, 버튼을 눌렀을 때 count
를 1씩 증가시키는 앱을 만들었다. 그리고 재렌더링을 확인하기 위해 콘솔을 찍어보았다.
이 간단한 앱에 useRef
를 이용한 변수를 하나 만들어 보자.
import React, { useState, useRef } from 'react';
function App() {
const [count, setCount] = useState(0);
const countRef = useRef(0);
const addCountState = () => {
setCount(count+1);
}
console.log(countRef);
console.log("렌더링...🎨");
return (
<div>
<p>state: {count}</p>
<button onClick={addCountState}>add state</button>
</div>
);
}
export default App;
useRef
를 import하는 것을 잊지 말고, countRef
라는 변수에 useRef
를 하나 만들어 보았다. 그리고 무엇이 담겨있는지 콘솔로 찍어보았다.
개념 공부에서 이야기했던 대로 {current: 초깃값}
이 찍힌다. 초깃값을 0으로 했으므로 {current: 0}
이 되겠다. 그래서 우리가 ref
에 접근하고 싶다면 countRef.current
이렇게 오브젝트의 값에 접근하는 방법과 똑같이 하면 되겠다.
import React, { useState, useRef } from 'react';
function App() {
const [count, setCount] = useState(0);
const countRef = useRef(0);
const addCountState = () => {
setCount(count+1);
}
const addCountRef = () => {
countRef.current = countRef.current + 1;
}
console.log(countRef);
console.log("렌더링...🎨");
return (
<div>
<p>state: {count}</p>
<p>Ref: {countRef.current}</p>
<button onClick={addCountState}> add state </button>
<button onClick={addCountRef}> add Ref </button>
</div>
);
}
export default App;
add State
버튼을 통해 state를 변경할 때마다 렌더링이 되지만, add Ref
를 통해 conutRef
를 변경할 때는 렌더링이 안 되는 것을 확인할 수 있다. add Ref
버튼을 눌러도 화면에 변화가 없어서 아무 일도 일어나지 않는 것 같지만, countRef
의 값은 변경되고 있다. addCountRef
안에서 콘솔을 찍어서 확인해 보면,
const addCountRef = () => {
countRef.current = countRef.current + 1;
console.log(countRef.current); // 추가
};
이렇게 countRef
의 값이 화면에서 확인할 수는 없지만 변경되고 있다는 것을 알 수 있다.
ref
의 차이import React, { useState, useRef } from 'react';
function App() {
const countRef = useRef(0);
let countVar = 0;
const addCountRef = () => {
countRef.current = countRef.current + 1;
console.log('ref: ', countRef.current);
}
const addCountVar = () => {
countVar = countVar + 1;
console.log('var: ', countVar);
}
return (
<div>
<p>Ref: {countRef.current}</p>
<p>Var: {countVar}</p>
<button onClick={addCountRef}> add Ref </button>
<button onClick={addCountVar}> add Var </button>
</div>
);
}
export default App;
이번에는 일반 변수와 useRef
의 차이를 알아보기 위해 countRef
와 countVar
을 가지고 비교해 볼 것이다. countRef
는 useRef
, countVar
는 let
을 이용해 만들었다.
두 변수 모두 렌더링을 유발하지 않기 때문에 화면의 변화는 없지만, 콘솔을 통해 값이 변화하고 있다는 사실을 알 수 있다.
그러면 화면을 업데이트 하기 위한state
를 하나 추가해 보자.
import React, { useState, useRef } from 'react';
function App() {
const [renderer, setRenderer] = useState(0);
const countRef = useRef(0);
let countVar = 0;
const addCountRef = () => {
countRef.current = countRef.current + 1;
console.log('ref: ', countRef.current);
}
const addCountVar = () => {
countVar = countVar + 1;
console.log('var: ', countVar);
}
const makeRender = () => {
setRenderer(renderer+1);
console.log('render🎨');
}
return (
<div>
<p>Ref: {countRef.current}</p>
<p>Var: {countVar}</p>
<button onClick={addCountRef}> add Ref </button>
<button onClick={addCountVar}> add Var </button>
<button onClick={makeRender}> render </button>
</div>
);
}
export default App;
renderer
라는 state
를 하나 선언하고, 버튼을 이용해 1씩 증가시켜 컴포넌트 렌더링이 일어나도록 만들었다. 그럼 이 세가지 버튼이 어떻게 동작하는지 눌러보자.
ref
는 렌더링이 일어나도 값이 그대로 유지가 되지만, 일반 변수인 countVar
는 렌더링 이후 0으로 다시 초기화되는 것을 볼 수 있다. 렌더링이 되도 렌더 이전의 값인 4부터 시작한다. 그 말은, 컴포넌트가 브라우저에 마운트된 순간부터 언마운트될 때까지 같은 값을 유지할 수 있다는 뜻이다.
import React, { useEffect, useRef } from 'react';
function App() {
return (
<div>
<input type="text" placeholder="ID" />
<button> 로그인 </button>
</div>
);
}
export default App;
간단한 로그인 Input창과 버튼을 만들었다. 이번 예제에서는 input창을 클릭하지 않아도 자동으로 focus(커서)를 주는 기능을 만들 것이다.
const inputRef = useRef();
useEffect(()=>{
console.log(inputRef);
}, []);
먼저 input에 달아줄 inputRef
를 만들었다. 그리고 렌더링 후 실행될 useEffect
를 만들고 의존성 배열에 빈 배열을 넣어 한 번만 실행되도록 했다.
useRef
를 초기화하지 않았기 때문에 undefined
가 뜬다. 이 undefined
의 자리에 input에 대한 참조값을 넣을 것이다. 그 방법은 다음처럼 간단하다.
<input ref={inputRef} />
이렇게 접근하고자 하는 DOM요소의 ref
속성에 useRef
가 담긴 변수를 달아주면,
current
자리에 input 오브젝트가 들어간다.
import React, { useEffect, useRef } from 'react';
function App() {
const inputRef = useRef();
useEffect(()=>{
console.log(inputRef);
inputRef.current.focus();
}, []);
return (
<div>
<input ref={inputRef} type="text" placeholder="ID" />
<button> 로그인 </button>
</div>
);
}
export default App;
inputRef.current
안에는 input 요소가 담겨있기 때문에 바로 접근해서 사용 가능하다. useEffect
안에서 focus가 되게끔 만들어뒀으므로 새로고침 후 바로 focus가 될 것이다.
DOM에 가할 수 있는 조작 모두 inputRef.current
를 통해 가능하다. 이를테면 input value를 출력하려면 console.log(inputRef.current.value)
이런 식으로 접근해서 value를 가져올 수 있다.