TL;DR
๋ฆฌ์กํธ์์ ๋ ๋๋ง ๋์ด์ผํ , ๋ ๋๋ง ์ ์ฌ์ฉ๋์ง ์๋ ๊ฐ๋ค์ ์ฌ์ฉํ๋ escape hatch
๊ธฐ๋ณธ
const ref = useRef(0);
console.log(ref) // { current:0 }
TypeScript
๋ณต์ฌ
โข
๋ ๋๋ง์ ์ ๋ฐ ์ํค๊ณ ์ถ์ง๋ ์๊ณ ๊ทผ๋ฐ ๋ ๋๋ง๊ฐ ์ ๋ณด๋ ์๊ณ ์ถ์ง ์์ ๋ ์ฌ์ฉํ๋ ๊ฒ.
โข
mutable, ๋ง๋๋ก ์์ ํ๊ณ ๋ถ๋ฌ์ค๊ธฐ๊ฐ ๊ฐ๋ฅํ๋ค. โ ๋ฆฌ์กํธ์์๋ ref์ ๋ณํ๋ฅผ ๊ฐ์งํ์ง ์์ผ๋ฏ๋ก immutable์ผ ํ์๊ฐ ์๋ค.
โข
๋ฐ๋๋ก ํ๋ฉด์ ๋ณด์ฌ์ผํ , ๋ ๋ํด์ผํ ์ ๋ณด์ธ ๊ฒฝ์ฐ์๋ state๋ก ๋ณด์กดํ๋ค.
๋ ๋๋ง ๋์ค์๋ ๊ฑด๋ค์ง ๋ง๋ผ
โข
๋ ๋๋ง ๊ณผ์ ์ค์ ํ์ํ ์ ๋ณด์ผ ๊ฒฝ์ฐ state์ ๋ณด์กดํค๋๊ฒ ๋ง์ โ ref.current๊ฐ ์ธ์ ๋ฐ๋๋์ง ๋ฆฌ์กํธ๋ ๋ชจ๋ฅด๋ฏ๋ก ๋ ๋๋ง ๋์ค์ ๋ณ๊ฒฝํ๋ ๋ก์ง์ ์์ฑํ ๊ฒฝ์ฐ ์์์น ์์ ํ๋์ ์ ๋ฐํ๋ค.
โข
๋ฆฌ์กํธ๋ ref์ ๋ํด ์ ๊ฒฝ ์ฐ์ง ์๋๋ค. ์ผ๋ฐ ์๋ฐ์คํฌ๋ฆฝํธ ๊ฐ์ฒด๋ผ๊ณ ์๊ฐํด์ผํ๋ค.
DOM
โข
๋ฆฌ์กํธ๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ ๋ ๊ฒฐ๊ณผ๋ฌผ์ DOM์ ์
๋ฐ์ดํธํ๋ค. โ ๊ฒฐ๊ณผ์ ์ผ๋ก ๊ฐ๋ฐ์๊ฐ ์ค์ ๋ก DOM์ ์กฐ์ํ๋ ๊ฒฝ์ฐ๋ ๋งค์ฐ ๋๋ฌผ์ง๋ง ๊ผญ ํ์ํ ์ผ์ด์ค๊ฐ ์กด์ฌํ ๊ฒฝ์ฐ ref๋ฅผ ์ฌ์ฉํ๋ค.
โฆ
ํฌ์ปค์ค, ์คํฌ๋กค, ์์น ๊ณ์ฐ
โข
๋น์ฐํ๊ฒ๋ ๋ค๋ฅธ ์ปดํฌ๋ํธ์ DOM ๋
ธ๋์ ์ ๊ทผํ ๋ ๋ ์ฌ์ฉํ ์ ์๋ค. โ forwardRef ํจ์
ref๋ก DOM ์ฐธ์กฐํ๋๋ฐ null์ดโฆ
โข
๋ฆฌ์กํธ๋ ref.current๋ฅผ commit๋ ์ค์ ํ๋ค.
โข
DOM ์
๋ฐ์ดํธ ์ด์ ์๋ ref.current๋ null๋ก ์กด์ฌํ๋ค.
โข
DOM ์
๋ฐ์ดํธ ์ดํ ๋ฆฌ์กํธ๋ ref.current๋ ํด๋น๋๋ DOM ๋
ธ๋๋ฅผ ์ฐธ์กฐํ๊ณ ์๋ค.
โข
์ฃผ๋ก ์ด๋ฒคํธ ํธ๋ค์ด์์ ref์ ์ ๊ทผํ๋ค. ๊ทผ๋ฐ ref๋ก ์คฌ๋๋ฐ ์ ๊ทผํ ๋งํ ์ด๋ฒคํธ ํธ๋ค๋ฌ๊ฐ ์๋ค? โ useEffect๋ฅผ ์ฌ์ฉํ์.
ref๋ก๋ DOM์ ์กฐ์ํ๋ฉด ์๋๋ค?
โข
๋ฆฌ์กํธ๊ฐ DOM์ ์ฃผ๋ ๋ณํ์์ conflict๋ฅผ ๋ง๋ค ์ ์๋ค.
โข
๋ฌผ๋ก ํ์ํ ๊ฒฝ์ฐ๊ฐ ์๋ค. ๊ทธ๋ด ๋์๋ ๋ฆฌ์กํธ๊ฐ state๋ก ๊ด๋ฆฌํ์ง์๋ DOM ๋
ธ๋๋ค์ ref๋ก ์กฐ์ํด์ผ ์ค๋ฅ๋ฅผ ์ต์ํ ํ ์ ์๋ค.
batching๊ณผ์ ์ธ์
โข
์์ ๋งํ๋ฏ์ด DOM ๋
ธ๋๋ฅผ ref๋ก ์ฐธ์กฐ ์ ํ์ฌ ์ค๋
์ท์์๋ ref.current๊ฐ null์ ๊ฐ์ง๊ณ ์์ ์ ์๋ค.
โข
๊ฑฐ๊ธฐ์ ๋ฆฌ์กํธ๋ state ์ฒ๋ฆฌ๋ฅผ batching ํ๋คโฆ. ์? ์ด์ฐํ ๊น.
function handleAdd() {
const newTodo = { id: nextId++, text: text };
setText('');
setTodos([ ...todos, newTodo]);
listRef.current.lastChild.scrollIntoView({
behavior: 'smooth',
block: 'nearest'
});
}
TypeScript
๋ณต์ฌ
โข
์ฐ๋ฆฌ ๋ชจ๋ ์๋ค์ํผ flush๋ฅผ ์ด์ฉํ์ฌ ํด๊ฒฐ โ ์ผ๋จ state ์
๋ฐ์ํธ ํ๊ณ ๋ค์ ํจ์ ์คํ์์ผ!
function handleAdd() {
const newTodo = { id: nextId++, text: text };
flushSync(() => {
setText('');
setTodos([ ...todos, newTodo]);
});
listRef.current.lastChild.scrollIntoView({
behavior: 'smooth',
block: 'nearest'
});
}
TypeScript
๋ณต์ฌ
ํ์ด๋จธ ์์
import { 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>
</>
);
}
TypeScript
๋ณต์ฌ
โข
ํ์ด๋จธ ๊ตฌํ ์ interval ID๋ ๋ ๋๋ง๊ฐ์ ์ฌ๋ผ์ง๋ฉด ์๋๋ค. ๋ ๋๋ง๊ฐ์ ์ฌ๋ผ์ง๋ค๋ฉด clearInterval์ ํ ๋ฐฉ๋ฒ์ด ์ฌ๋ผ์ง๋ค. โ ์ด๋ Ref๊ฐ ๋ฑ์ฅํ๋ฉด ํด๊ฒฐํ ์ ์๋ค.