Ref는 references (참조) 라는 뜻이다.
원래 리액트의 ref는 특정 요소에 접근하는데 사용하는 props 인데, 리액트에서 DOM에 접근하고자 할때 사용한다.
HTML 에서 태그에 id 값을 부여하고, 자바스크립트에서 getElementById 로 그 id 에 접근해서 DOM 을 조작하는 것처럼, ref에 값을 줌으로써 DOM에 접근할 수 있다.
리액트에서는 jsx 문법을 이용해서 컴포넌트들을 작성하고, 그 컴포넌트들을 재사용한다.
만약에 HTML 과 같이 아이디값이 존재한다면, 그 컴포넌트들이 재사용 될때마다 id값을 가진 요소가 재렌더링 될것이고, 이렇게 된다면 동일 아이디를 가진 요소들이 여러개가 생기기 때문에 리액트에서는 id 값 대신 Ref 를 사용한다.
리액트 공식문서에서 useRef 를 위와 같이 정의하고 있지만, 이해가 되지 않았다.
다른 여러 글들과 영상들을 참고해서 저 공식문서 문구들을 해석해보았다.
useRef 는 함수형 컴포넌트에서 사용되는 Hook 인데, ref 객체를 리턴한다.
import { useRef } from "react";
function App() {
const inputEl = useRef('');
console.log("inputEl : ", inputEl);
return <></>;
}
export default App;
보면 current 라는 key 를 가진 객체가 반환되는 것을 볼 수 있다.
저 current 는 useRef() 안에 넣어준 초깃값이다.
current 값을 조작해서 초깃값을 수정해줄 수 도 있다.
import { useRef } from "react";
function App() {
const inputEl = useRef('');
inputEl.current = 1;
console.log(inputEl);
return <></>;
}
export default App;
라는 말은 컴포넌트가 계속해서 재렌더링이 되어도, 컴포넌트가 언마운트 되기 전까지 (즉, 화면에서 사라지기 전까지 )는 값을 그대로 유지할 수 가 있다.
useState 를 사용할때, state 값이 변경 될때마다 재렌더링이 발생한다.
또한 함수형 컴포넌트는 말그대로 함수이기 때문에, 함수가 불려진 후에는 함수 안의 변수들이 다시 초기화가 된다.
하지만 useRef 를 사용하면 Ref 안의 값들이 아무리 변경되어도, 재렌더링이 발생하지 않고 컴포넌트 안의 변수 값들이 그대로 유지가 된다.
그렇기 때문에 state 대신 ref 를 사용한다면, 불필요한 렌더링을 막을 수 있다.
또한 state 를 사용해서 값을 변경해서 재렌더링이 발생하더라도 ref 안에 있는 값은 그대로 유지가 된다.
변경시 렌더링을 발생시키지 말아야할 값을 다룰때 용이하다.
위에 말했던것처럼 ref 를 이용하면 DOM에 접근할 수 있다.
대표적으로 input 요소를 클릭하지 않아도 focus 를 주고 싶을때 사용한다.
예를 들어 로그인 화면을 클릭했을때, 자동적으로 아이디를 입력하는 칸에 포커스를 줌으로써 편리하게 할 수 있다.
import { useState } from "react";
function App() {
const [count, setCount] = useState(0);
const setCountHandler = () => {
setCount(count + 1);
};
console.log("재렌더링이 됩니다.");
return (
<>
<p>숫자 : {count}</p>
<button onClick={setCountHandler}>값 변경</button>
</>
);
}
export default App;
버튼을 클릭할때마다 count state 값이 변경되도록했고, 렌더링이 될때마다 콘솔창이 띄어지도록 했다.
버튼을 클릭하면 저 콘솔창도 클릭한 수에 맞게 띄어졌는데,
즉 state 값이 변경될때마다 화면이 재렌더링 됐다.
import { useRef, useState } from "react";
function App() {
const [count, setCount] = useState(0);
const countRef = useRef(0);
const setCountHandler = () => {
setCount(count + 1);
console.log("count State의 값은", count);
};
console.log("재렌더링 됩니다.");
const countRefHandler = () => {
countRef.current = countRef.current + 1;
console.log("countRef의 값은 ", countRef.current);
};
return (
<>
<p>State 값 : {count}</p>
<button onClick={setCountHandler}>State 값 변경</button>
<p>Ref 값 : {countRef.current}</p>
<button onClick={countRefHandler}>Ref 값 변경</button>
</>
);
}
export default App;
useRef 를 사용한 버튼을 하나 더 추가해서 값을 비교해보았다.
처음에 새로고침을 누르면 "재렌더링 됩니다." 라는 콘솔창이 나왔고, state 변경 버튼을 누를때마다 재렌더링이 되었다.
하지만 ref 값 변경 버튼을 여러번 눌러도 재렌더링 콘솔창이 띄어지지 않았다.
재렌더링이 이루어지지 않았기 때문에 화면상에서도 Ref 값이 변경된것처럼 보이지 않았다.
state 값 변경 버튼을 한번 누르니, ref 값도 다시 재렌더링 된것을 확인할 수 있었다.
import { useRef, useState } from "react";
function App() {
const [render, setRender] = useState('');
const countRef = useRef(0);
let countNum = 0;
const countRefHandler = () => {
countRef.current += 1;
console.log("ref 값 : ", countRef.current);
};
const setNumHandler = () => {
countNum += 1;
console.log("Num 값 : ", countNum);
};
const renderHandler = () => {
setRender('렌더링 됐습니다.');
console.log(render);
};
return (
<>
<button onClick={renderHandler}>렌더링 버튼</button>
<p>Num 값 : {countNum}</p>
<button onClick={setNumHandler}>State 값 변경</button>
<p>Ref 값 : {countRef.current}</p>
<button onClick={countRefHandler}>Ref 값 변경</button>
</>
);
}
export default App;
버튼을 3개 만들고, 하나는 useState 를 사용한 재렌더링을 위한 버튼,
하나는 ref 값 변경 버튼, 하나는 그냥 let 변수 값을 변경해주는 버튼이다.
밑의 사진은 렌더링 버튼을 누른후, 재렌더링이 된 후의 사진인데, Num 값은 변경이 되지 않았지만, Ref 값은 재렌더링이 되어서 값이 변경된 걸 볼수 있다.
그 이유는
재렌더링이 된다는 것은 함수 컴포넌트가 다시 호출 된다는 것이고, 다시 호출되면서 변수들이 초기화 되기 때문에
Num 값은 초깃값인 0 으로 다시 변경되므로 값이 변경되지 않는것이다.
또한 Num 값은 다시 초기화가 되어서 숫자가 다시 0부터 시작되지만, ref 는 그 값을 컴포넌트가 언마운트 되기 전까지 가지고 있으므로, 값이 누적된다.
import { useEffect } from "react";
import { useRef } from "react";
function App() {
const idRef = useRef();
useEffect(() => {
console.log(idRef);
},[]);
return (
<>
<input ref={idRef} type={"text"} placeholder="아이디"></input>
<button>로그인</button>
</>
);
}
export default App;
아이디를 입력하는 인풋창에 ref 속성을 입력해준후, 렌더링 될때 idRef 를 출력하도록 했다.
ref.current 안에 input 값 그대로 담긴걸 볼 수 있다.
import { useEffect } from "react";
import { useRef } from "react";
function App() {
const idRef = useRef();
useEffect(() => {
idRef.current.focus();
},[]);
return (
<>
<input ref={idRef} type={"text"} placeholder="아이디"></input>
<button>로그인</button>
</>
);
}
export default App;
위처럼 focus 메서드를 사용해서 렌더링 될때 input 창에 포커스가 가도록 하였다.