useRef는 왜? 쓰는걸까? 컴포넌트 내부 변수관리란??
함수형 컴포넌트에서 useRef 를 부르면 ref 오브젝트를 반환해준다
const ref = useRef(value)
ref 오브젝트는
언제쓸까?
저장공간으로 사용!
DOM 요소에 접근
로그인화면이 보여질때 아이디를 넣는 input 을 굳이 클릭하지 않아도
자동으로 focus 를 되어있게 해주면 바로 키보드를 사용해서 아이디를 입력할 수 있기 때문에 짱편리!
이 코드는 함수형 컴포넌트 , state up 버튼을 누를때마다 렌더링이 된다!
import { useState } from "react";
function App() {
const [count, setCount] = useState(0);
console.log('렌더링')
const increaseCountState = () => {
setCount(count + 1);
};
return (
<div>
<p>State: {count}</p>
<button onClick={increaseCountState}>State up</button>
</div>
);
}
export default App;
useRef 로 매번 렌더링되는 현상을 막아줄 수 있다.
일단 먼저 imort 에 useRef 를 불러오고
import { useState, useRef } from "react";
countRef 를 선언해준다.
const countRef = useRef(0);
이 countRef 가 어떻게 동작하는지 console.log 로 찍어보니
이렇게 불려오는 것을 볼 수 있다.
보다시피 ref 는 하나의 object 이다!
그리고 ref 안에는 current 값, 내가 설정해준 초기값 0 이 들어있다.
ref 안에 있는 값에 접근하고 싶으면
countRef.current 로 출력해주면 된다.
버튼을 하나 더 만들어 줘보자 !
const increaseCountRef= () => {
countRef.current = countRef.current + 1;
}
<button onClick={increaseCountRef}>Ref up</button>
버튼을 만들고 click 했을 때 increaseCountRef 함수가 동작하게 만들어 줬다.
increaseCountRef 함수는 countRef 의 current 값이 +1 씩 증가하는 함수이다.
이제 화면에는
이렇게 두개의 버튼이 있는데 ,
state up 버튼을 누를때마다 콘솔창에 렌더링이 찍히는 것을 볼 수가 있다.
state 는 한 번 실행될 때 마다 계속 App() 이 재렌더링 되기 때문이다!
자 이번에는 Ref up 버튼을 눌러봤는데 , 아무일도 일어나지 않는다
왜!?!?
Ref 는 아무리 수정해도 이 App 컴포넌트 전체를 다시 렌더링 시키지 않는다.
App 컴포안의 countRef 함수가 계속 증가하고 있는것은 맞음!
근데 렌더링이 되지 않고 있기 때문에 화면이 업데이트 되지 않는 것 .
여기서 이제 state up 버튼을 누르면 화면이 업데이트 되기 때문에
그동안 업데이트 되지 않았지만 클릭됨으로써 +1 되었던 Ref 값까지 업데이트 된다.
최종코드
import { useState, useRef } from "react";
function App() {
const [count, setCount] = useState(0);
const countRef = useRef(0);
console.log(countRef);
console.log("렌더링");
const increaseCountState = () => {
setCount(count + 1);
};
const increaseCountRef = () => {
countRef.current = countRef.current + 1;
console.log("Ref:", countRef.current);
};
return (
<div>
<p>State: {count}</p>
<p>Ref : {countRef.current}</p>
<button onClick={increaseCountState}>State up</button>
<button onClick={increaseCountRef}>Ref up</button>
</div>
);
}
export default App;
컴포넌트의 가장 자주 바뀌는 값을 state 에 넣는다면 성능에 안좋을텐데
이때 state 대신 useRef 를 넣으면 렌더링 되지 않아서 state 보다 성능에 영향주지 않을것!
import { useRef } from "react";
function App() {
const [renderer, setRenderer] = useState(0);
const countRef = useRef(0);
let countVar = 0;
const increaseRef = () => {
countRef.current += 1;
console.log("ref", countRef.current);
};
const increaseVar = () => {
countVar += 1;
console.log("var", countVar);
};
return (
<div>
<p>Ref : {countRef.current}</p>
<p>Var : {countVar}</p>
<button onClick={increaseRef}>Ref up</button>
<button onClick={increaseVar}>Var up</button>
</div>
);
}
export default App;
이 코드를 실행시켰을 때 Ref 와 Var 렌더링 되지 않는다.
그래서 이 업데이트 되지 않는 아이들을 렌더링 해주기 위해서
const doRendering = () => {
setRenderer(renderer + 1);
};
doRendering 함수를 실행하는 버튼을 만들어주고
ref 와 var 이 렌더링되게 해주었다!
근데 왜 Ref 만 렌더링되고 Var 은 0 으로 되어있을까?
컴포넌트가 렌더링 된다는 것은 컴포넌트를 나타내는 함수가 다시 불린다는 것
즉 초기화가 된다는 것이다! 그래서 var은 countVar = 0 으로 초기화가 됨.
근데 ref 는 다르다. 아무리 컴포넌트가 렌더링 되더라도 ref 의 값을 유지한다!!
mounting 된 시점부터 해제된 시점까지 계속 제 값을 가지고 있음 .
ref 는 렌더링 이후에도 이전의 값부터 시작하는 것을 볼 수 있다.
하지만 var 은 값이 초기화되어 처음부터 시작한다.
더 확실한 비교를 위해 Ref Var 을 출력하는 함수를 만들어 보았다.
Ref up 버튼을 누르고, Var up 버튼을 누른 후 Ref var 을 클릭해주니,
ref:2 var:2 가 나왔다. 그리고 나서 Render 로 렌더링을 해주고 다시, Ref var 버튼을 클릭해주니, ref 는 초기화되지 않고 그대로, 변수는 0 으로 초기화가 되었다. 이게 ref와 그냥 변수의 차이이다.
최종코드
import { useRef } from "react";
function App() {
const [renderer, setRenderer] = useState(0);
const countRef = useRef(0);
let countVar = 0;
const doRendering = () => {
setRenderer(renderer + 1);
};
const increaseRef = () => {
countRef.current += 1;
console.log("ref", countRef.current);
};
const increaseVar = () => {
countVar += 1;
console.log("var", countVar);
};
// 현재 ref 안에 있는 값과 변수안에 있는 값을 출력하는 함수
const printResults = () => {
console.log(`ref: ${countRef.current} var : ${countVar}`);
};
return (
<div>
<p>Ref : {countRef.current}</p>
<p>Var : {countVar}</p>
<button onClick={doRendering}>Render!</button>
<button onClick={increaseRef}>Ref up</button>
<button onClick={increaseVar}>Var up</button>
<button onClick={printResults}>Ref var</button>
</div>
);
}
export default App;
useEffect 를 사용해서 up 을 눌렀을 때 count 가 올라갔으면 좋겠어.
라고 하면 보통 useState 를 만들어서 할텐데,
그렇게 하면 무한루프에 빠진다.
import { useState, useRef, useEffect } from "react";
function App() {
const [count, setCount] = useState(1);
const [renderCount, setRenderCount] = useState(1);
useEffect(() => {
console.log('렌더링')
setRenderCount(renderCount+1);
});
return (
<div>
<p>Count : {count}</p>
<button onClick={() => setCount(count + 1)}>up </button>
</div>
);
}
export default App;
악! !! 무한루프에 갇혀서 무한 렌더링 중 ....
왜 이러는걸까여?
up 버튼을 클릭하면 countState 가 업데이트 되어 useEffect 가 불릴것 .
근데 이 useEffect 안에도 renderCount state를 업데이트 하는 코드가 있다.
이렇게 되면
Count state 가 불렸고, useEffect 가 업데이트 됐고, renderCount 도 업데이크 됐고..? 그럼 또 useEffect 가 불리고? 또 enderCount 도 업데이크 됐고..? 그럼 또 useEffect 가 불리고? 이게 무한 반복이 되는거임.....^^
이럴때 사용하는게 ref!!!
import { useState, useRef, useEffect } from "react";
function App() {
const [count, setCount] = useState(1);
// const [renderCount, setRenderCount] = useState(1);
const renderCount = useRef(1);
useEffect(() => {
renderCount.current++;
console.log("렌더링수:", renderCount.current);
});
return (
<div>
<p>Count : {count}</p>
<button onClick={() => setCount(count + 1)}>up </button>
</div>
);
}
export default App;
이렇게 무한루프에 빠지지 않고 버튼 클릭만큼 깔끔하게 렌더링이 되는 것을 볼 수 있음
input 을 클릭하지 않아도 focus 되게 만들어주려면
useRef 가 찰떡 !
간단하다! input 안에 ref={들어갈 값} 을 작성해주고
<input ref={inputRef} type="text" placeholder="아이디를 입력하세요" />
렌더링될때 처음에만 업데이트 되는 useEffrect,[] 를 써서
inputRef.current.focus();
을 사용해주면
페이지에 들어왔을 때 바로 input 창이 focus 됨!
useEffect(() => {
inputRef.current.focus();
}, []);
여기서 더해서
아이디를 입력하고 로그인 버튼을 눌렀을 때 alert 가 뜨기 + alert 창을 닫았을 때 자동으로 다시 input 창이 focus 되게 만들어보자
<button onClick={login}>로그인</button>
버튼을 클릭했을때 {login} 이 실행
const login = () => {
alert(`welcome ${inputRef.current.value}!`);
inputRef.current.focus();
};
이 login 함수는 버튼을 틀릭했을때 alert창을 띄우고,
그 다음 다시 inputRef 를 focus 하게 만든다.
최종코드
import { useState, useRef, useEffect } from "react";
function App() {
const inputRef = useRef();
useEffect(() => {
// console.log(inputRef);
inputRef.current.focus();
}, []);
const login = () => {
alert(`welcome ${inputRef.current.value}!`);
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} type="text" placeholder="아이디를 입력하세요" />
<button onClick={login}>로그인</button>
</div>
);
}
export default App;