19. useRef() hook
useRef()는 레퍼런스 객체를 반환한다.
레퍼런스 객체에는 .current라는 속성이 있는데 이것은 현재 레퍼런스하고 있는 엘리먼트를 의미
const refContainer = useRef(초깃값)
아래와 같은 파라미터로 들어온 초깃값으로 초기화된 레퍼런스 객체를 반환한다.
{current: value}
만약 초깃값이 null이라면 .current의 값이 null인 레퍼런스 객체가 반환된다.
이렇게 반환된 레퍼런스 객체는 컴포넌트 라이프타임 전체에 걸쳐서 유지된다. 즉 컴포넌트가 마운트 해제 전까지는 계속 유지된다.
저장공간
State의 변화 -> 렌더링 -> 컴포넌트 내부 변수들 초기화
Ref의 변화 -> No 렌더링 -> 변수들의 값이 유지됨
변수값의 변화 -> No 렌더링 -> 값이 초기화됨.
import { useState,useRef } from "react";
export default function MyComponent_19_1(){
const [count,SetCount] = useState(0)
const countRef = useRef(0)
let countVar = 0
const incrementCountState = () => {
SetCount(count + 1)
console.log('state:',count)
console.log('ref:',countRef)
console.log('var:',countVar)
}
const incrementCountRef = () => {
countRef.current = countRef.current + 1
console.log('state:',count)
console.log('ref:',countRef)
console.log('var:',countVar)
}
const incrementCountVar = () => {
countVar = countVar + 1
console.log('state:',count)
console.log('ref:',countRef)
console.log('var:',countVar)
}
return(
<div>
<p>State: {count}</p>
<p>Ref: {countRef.current}</p>
<p>Var: {countVar}</p>
<button onClick={incrementCountState}>State 증가</button>
<button onClick={incrementCountRef}>Ref 증가</button>
<button onClick={incrementCountVar}>Var 증가</button>
</div>
)
}
변화는 감지해야 하지만 그 변화가 랜더링을 발생시키면 안되는 경우 사용할 수 있다.
import { useState,useRef,useEffect } from "react";
export default function MyComponent_19_2(){
const [count,setCount] = useState(0)
const [renderCount,setRenderCount] = useState(0)
const renderRef = useRef(0)
// 의존성 배열이 생략되었으므로 컴포넌트 업데이트시마다 실행
// 생성시 초기값 0, 생성후 실행되므로 1
useEffect(()=>{
// console.log('렌더링:',renderCount)
// setRenderCount(renderCount+1)
console.log('렌더링:',renderRef.current)
renderRef.current = renderRef.current + 1
})
return(
<div>
<p>State: {count}</p>
<p>render State: {renderCount}</p>
<p>Ref: {renderRef.current}</p>
<button onClick={() => setCount(count + 1)}>State Count 증가</button>
</div>
)
}
useRef를 이용해서 DOM에 접근하기
const ref = useRef(value)
...
<input ref={ref}/>
import { useRef, useEffect } from "react";
export default function MyComponent_19_3(){
const inputRef = useRef()
useEffect( () => {
console.log(inputRef)
inputRef.current.focus()
},[] )
const login = () => {
alert(`Hello! ${inputRef.current.value} ~`)
inputRef.current.value = ""
inputRef.current.focus()
}
const handleOnKeyPress = e => {
if (e.key == 'Enter'){
login()
}
}
return(
<div>
<input ref={inputRef} type="text" placeholder="username" onKeyUp={handleOnKeyPress} />
<button onClick={login}>로그인</button>
</div>
)
}
20. stopwatch app
src/20/StopWatch.jsx
import { useState,useEffect,useRef } from "react";
import './StopWatch.css'
export default function StopWatch(){
const [isRunning, setIsRunning] = useState(false);
const [elapsedTime, setElapsedTime] = useState(0)
const intervalIdRef = useRef(null);
const startTimeRef = useRef(0);
useEffect(()=>{
if(isRunning){
intervalIdRef.current = setInterval(()=>{
setElapsedTime(Date.now() - startTimeRef.current);
},10);
}
return () => {
clearInterval(intervalIdRef.current);
}
},[isRunning]);
function start(){
setIsRunning(true);
startTimeRef.current = Date.now() - elapsedTime;
}
function stop(){
setIsRunning(false);
}
function reset(){
setElapsedTime(0);
setIsRunning(false);
}
function formatTime(){
let hours = Math.floor( elapsedTime / ( 1000 * 60 * 60 ));
let minutes = Math.floor( elapsedTime / ( 1000 * 60 ) % 60 );
let seconds = Math.floor( elapsedTime / ( 1000 ) % 60 );
let milliseconds = Math.floor(( elapsedTime % 1000 ) / 10 );
hours = String(hours).padStart(2,"0");
minutes = String(minutes).padStart(2,"0");
seconds = String(seconds).padStart(2,"0");
milliseconds = String(milliseconds).padStart(2,"0");
return `${minutes}:${seconds}:${milliseconds}`;
}
return(
<div className="stopwatch">
<div className="display">{formatTime()}</div>
<div className="controls">
<button className="start-button" onClick={start}>Start</button>
<button className="stop-button" onClick={stop}>Stop</button>
<button className="reset-button" onClick={reset}>Reset</button>
</div>
</div>
);
}
src/20/StopWatch.css
body{
display: flex;
flex-direction: column;
align-items: center;
background-color: hsl(0, 0%, 95%);
}
.stopwatch{
display: flex;
flex-direction: column;
align-items: center;
border: 5px solid;
border-radius: 50px;
background-color: white;
padding: 30px;
}
.display{
font-size: 5rem;
font-family: monospace;
font-weight: bold;
color: hsl(0, 0%, 30%);
text-shadow: 2px 2px 2px hsla(0, 0%, 0%, 0.75);
margin-bottom: 25px;
}
.controls button{
font-size: 1.5rem;
font-weight: bold;
padding: 10px 20px;
margin: 5px;
min-width: 125px;
border: none;
border-radius: 10px;
cursor: pointer;
color: white;
transition: background-color 0.5s ease;
}
.start-button{
background-color: hsl(115, 100%, 40%);
}
.start-button:hover{
background-color: hsl(115, 100%, 35%);
}
.stop-button{
background-color: hsl(0, 90%, 50%);
}
.stop-button:hover{
background-color: hsl(0, 90%, 40%);
}
.reset-button{
background-color: hsl(205, 100%, 60%);
}
.reset-button:hover{
background-color: hsl(205, 100%, 50%);
}