You will learn
- ์ปดํฌ๋ํธ์ ref๋ฅผ ์ถ๊ฐ ํ๋ ๋ฐฉ๋ฒ
- ref๊ฐ ์ ๋ฐ์ดํธ ํ๋ ๋ฐฉ๋ฒ
- ref์ state์ ์ฐจ์ด
- ref ์์ ํ๊ฒ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ
Ref๋
๊ณต์๋ฌธ์์์ ์ํ๋ฅผ ์ปดํฌ๋ํธ์ ๋ฉ๋ชจ๋ฆฌ๋ผ๊ณ ํ๋ค. ์ํ๊ฐ ๋ณํ ๋๋ง๋ค ๊ทธ์ ๋ง๋ ์ต์ ์ํ์ ์ปดํฌ๋ํธ๊ฐ ๋ค์ ๊ทธ๋ ค์ง๊ธฐ ๋๋ฌธ์ธ๊ฑด๋ฐ, ์ด๋ค ๊ฐ๋ค์ ์ปดํฌ๋ํธ๊ฐ ๊ธฐ์ตํ์ผ๋ฉด ์ข๊ฒ ์ง๋ง ๋ณํ ๋๋ง๋ค ์๋ก ๋ ๋๋ง ํ์ง๋ ์์์ผ๋ฉด ์ข๊ฒ ์ผ๋ฉด ํ๋๊ฒ๋ค์ด ์๋ค.
๋ํ์ ์ธ ์์๋ก ํ์ด๋จธ๊ฐ ์๋ค.
mport { useState, useRef } from 'react';
export default function Stopwatch() {
const [startTime, setStartTime] = useState(null);
const [now, setNow] = useState(null);
const intervalRef = useRef(null);
function handleStart() {
setStartTime(Date.now());
setNow(Date.now());
clearInterval(intervalRef.current);
intervalRef.current = setInterval(() => {
setNow(Date.now());
}, 10);
}
function handleStop() {
clearInterval(intervalRef.current);
}
let secondsPassed = 0;
if (startTime != null && now != null) {
secondsPassed = (now - startTime) / 1000;
}
return (
<>
<h1>Time passed: {secondsPassed.toFixed(3)}</h1>
<button onClick={handleStart}>
Start
</button>
<button onClick={handleStop}>
Stop
</button>
</>
);
}
์คํํธ ๋ฒํผ์ ๋๋ฅด๋ฉด setInterval์ด ์คํ๋๋ฉด์ 10ms ๋ง๋ค now ์ํ๋ฅผ ์
๋ฐ์ดํธ ํ๋ค. ๊ทธ๋ฆฌ๊ณ ๊ทธ๋๋ง๋ค secondsPassed ๊ฐ์ด ๊ณ์ฐ๋๊ณ ๊ทธ๊ฑธ ํ๋ฉด์ ํ์ํ๋ค.
๊ทธ๋ฐ๋ฐ ์ด๊ฑธ ๋ฉ์ถ๋ ค๋ฉด setInterval์ด ๋ฐํํ id๋ฅผ ์ฌ์ฉํด ์ ๋ฉ์ถฐ์ฃผ์ด์ผ ํ๋๋ฐ(clearInterval(id)) ์ด ๊ฐ์ ๋ ๋๋ง๊ณผ ๊ด๋ จ์ด ์๊ธฐ ๋๋ฌธ์ ref๋ก ์ฌ์ฉํ๋ค.
state vs ref
ํ์ด๋จธ id๋ฅผ ์ํ๋ก ํ๋ฉด ์๋๋?
์ฌ์ค ํ์ด๋จธ Id๋ฅผ ์ํ๋ก ๊ด๋ฆฌํด๋ ์ ์์ ์ผ๋ก ์๋ ํ๋ค.
import { useState, useRef } from 'react';
export default function Stopwatch() {
const [startTime, setStartTime] = useState(null);
const [now, setNow] = useState(null);
const [intervalId, setIntervalId] = useState(null);
function handleStart() {
setStartTime(Date.now());
setNow(Date.now());
clearInterval(intervalId);
setIntervalId(setInterval(() => {
setNow(Date.now());
}, 10));
}
function handleStop() {
clearInterval(intervalId);
}
let secondsPassed = 0;
if (startTime != null && now != null) {
secondsPassed = (now - startTime) / 1000;
}
return (
<>
<h1>Time passed: {secondsPassed.toFixed(3)}</h1>
<button onClick={handleStart}>
Start
</button>
<button onClick={handleStop}>
Stop
</button>
</>
);
}
๊ทธ๋ฐ๋ฐ ์ ์ํ๋ก ํ๋ฉด ์๋๊ณ ref๋ก ์ฌ์ฉํด์ผํ ๊น? ๋ฌธ์์์ ์ด์ ๋ํ ๋ต์ ์ฐพ์ง๋ ๋ชปํ๋ค.
ref์ ๋ด๋ถ ์๋ฆฌ
๋ฆฌ์กํธ ๋ด๋ถ ์๋ฆฌ๋ setter ํจ์ ์์ด ์ฌ์ฉํ๋ ์ํ์ ํก์ฌํ๋ค๊ณ ํ๋ค.
// Inside of React
function useRef(initialValue) {
const [ref, unused] = useState({ current: initialValue });
return ref;
}
์์ ๋ฌธ์
1.
export default function Chat() {
const [text, setText] = useState('');
const [isSending, setIsSending] = useState(false);
let timeoutID = null;
function handleSend() {
setIsSending(true);
timeoutID = setTimeout(() => {
alert('Sent!');
setIsSending(false);
}, 3000);
}
function handleUndo() {
setIsSending(false);
clearTimeout(timeoutID);
}
return (
//...
handleUndo๋ฅผ ๋๋ฌ๋ ์ทจ์๋์ง ์๋ ์ด์ ๋ timeoutID๋ฅผ ์ผ๋ฐ ๋ณ์๋ก ์ ์ธํ๊ธฐ ๋๋ฌธ์ด๋ค. ๋ณด๋ด๊ธฐ ๋ฒํผ์ ๋๋ฅด๋ฉด ์ํ๊ฐ ๋ณ๊ฒฝ๋๋ฉด์ ์ปดํฌ๋ํธ๊ฐ ๋ฆฌ๋ ๋๋ง ๋๋๋ ์ผ๋ฐ ๋ณ์๋ ์ฌ๋ผ์ง๋ค. ๊ทธ๋์ undo ๋ฒํผ์ ์๋ง clearTimeout(null) ์ ํธ์ถํ๊ฒ ๋ ๊ฑฐ๊ณ ๊ทธ๋์ ์ทจ์๋์ง ์๋๊ฒ.
2.
export default function Toggle() {
const isOnRef = useRef(false);
return (
<button onClick={() => {
isOnRef.current = !isOnRef.current;
}}>
{isOnRef.current ? 'On' : 'Off'}
</button>
);
}
ํ ๊ธ ๋ฒํผ์ ๋๋ฌ๋ On๊ณผ Off ๋ฌธ๊ตฌ๊ฐ ๋ฐ๋์ง ์๋ ์ด์ ๋, ref๋ ๋ฆฌ๋ ๋๋ง์ ์ด๋ฐํ์ง ์๊ธฐ ๋๋ฌธ์ด๋ค. console.log(isOnRef.current)๋ฅผ ์ฐ์ด๋ณด๋ฉด ๋ฒํผ์ ๋๋ฅผ๋๋ง๋ค ๊ฐ์ ๋ณํ๋๊ฑธ ํ์ธํ ์ ์๋ค.
3. debouncing
let timeoutID;
function DebouncedButton({ onClick, children }) {
return (
<button onClick={() => {
clearTimeout(timeoutID);
timeoutID = setTimeout(() => {
onClick();
}, 1000);
}}>
{children}
</button>
);
}
export default function Dashboard() {
return (
<>
<DebouncedButton
onClick={() => alert('Spaceship launched!')}
>
Launch the spaceship
</DebouncedButton>
<DebouncedButton
onClick={() => alert('Soup boiled!')}
>
Boil the soup
</DebouncedButton>
<DebouncedButton
onClick={() => alert('Lullaby sung!')}
>
Sing a lullaby
</DebouncedButton>
</>
)
}
๋๋ฐ์ด์ฑ์ด๋ ์งง์ ์๊ฐ์ ์ด๋ฒคํธ๊ฐ ๋ง์ด ๋ฐ์ํ ๋ ๋ง์ง๋ง ํ๋ฒ๋ง ์ด๋ฒคํธ๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ ๋งํ๋ค.
์์์ ๋ฒํผ์ ๋๋ฅผ๋ ๋ง๋ค ๊ธฐ์กด ํ์ด๋จธ๋ฅผ ์ง์ฐ๊ณ ์๋ก์ด ํ์ด๋จธ๋ฅผ ์ค์ ํด 1์ด ๋ค์ ์๋ฆผ์ฐฝ์ด ๋ฑ์ฅํ๋ค.
์ ์ฝ๋์ ๋ฌธ์ ๋ ๊ฐ ๋ฒํผ๋ค๋ง๋ค ๋
๋ฆฝ์ ์ผ๋ก ๋๋ฐ์ด์ฑ ์ฒ๋ฆฌ๋ฅผ ํ์ง ๋ชปํ๋ค๋ ๊ฒ์ธ๋ฐ, ๊ฐ๋จํ let timeoutID ๋ผ๊ณ ์ปดํฌ๋ํธ ๋ฐ์์ ๋ณ์๋ฅผ ์ ์ธํ๊ณ ๊ทธ๊ฑธ ์ฌ์ฉํ๊ณ ์๊ธฐ ๋๋ฌธ์ ๋ชจ๋ ๋ฒํผ๋ค์ด ํ๋์ ๋ณ์๋ฅผ ๊ณต์ ํ๊ณ ์๊ธฐ ๋๋ฌธ์ด๋ค. ๋ฒํผ ์ปดํฌ๋ํธ ์์์ ref๋ฅผ ์ฌ์ฉํด ํด๊ฒฐํ๋ค.
4. ์ต์ state ์ฝ๊ธฐ
export default function Chat() {
const [text, setText] = useState('');
const textRef = useRef(text);
function handleChange(e) {
setText(e.target.value);
textRef.current = e.target.value;
}
function handleSend() {
setTimeout(() => {
alert('Sending: ' + textRef.current);
}, 3000);
}
return (
<>
<input
value={text}
onChange={handleChange}
/>
<button
onClick={handleSend}>
Send
</button>
</>
);
}
์ค์ ๋ก ์ด๋ ๊ฒ ์ค๊ณํด์ผ ํ ๊ฒฝ์ฐ๋ ๋ง์ง ์์ง๋ง (Usually, this behavior is what you want in an app. However, there may be occasional cases where you want some asynchronous code to read the latest version of some state.), ์์ ์ฝ๋์ฒ๋ผ ์ต์ ์ํ๋ฅผ ์ฝ๊ธฐ ์ํด ref๋ฅผ ์ฌ์ฉํ ์๋ ์๋ค.
alert() ํจ์๊ฐ ํธ์ถ๋๋ฉด์ ์ ๋ฌ๋ฐ์ ๋งค๊ฐ๋ณ์๋ ์ํ๊ฐ ๋ณ๊ฒฝ๋์ด ์ปดํฌ๋ํธ๊ฐ ๋ฆฌ๋ ๋๋ง ๋์ด๋ ๊ฐ์ ๊ฐ์ ๊ฐ์ง๊ณ ์๊ธฐ ๋๋ฌธ์ด๋ค. (๋ฆฌ์กํธ๋ ์ํ๋ฅผ ๋ณ๊ฒฝํ์ง ์๊ณ setter๋ก ์์ ์๋ก์ด ๊ฐ์ผ๋ก ๋ณ๊ฒฝํ๋ค.)

