2022๋
3์์ ๋ฐํ๋ React 18์ ์ฑ๋ฅ ํฅ์๊ณผ ๋ ๋๋ง ์์ง ๊ฐ์ ์ ์ด์ ์ด ๋ง์ถฐ์ ธ ์๋ค.
React 18์ ๋์์ฑ(concurrent) React๋ฅผ ๊ฐ์กฐํ๋ฉฐ ์ฌ๋ฌ ๊ฐ์ง ์๋ก์ด ๊ธฐ๋ฅ๋ค์ ์ถ์ํ์๋๋ฐ,
ํ๋ก์ ํธ์ ํด๋น ๊ธฐ๋ฅ๋ค์ ์ ์ฉํ๊ธฐ์ ์์ ์ด ์ค ๋ช ๊ฐ์ง ๊ธฐ๋ฅ๋ค์ ์์ธํ ์์๋ณด๊ณ ์ ํ๋ค.
๐ฉโ๐ซ React 18์ ์ ํ๋ฆฌ์ผ์ด์
์ด๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฝ๋์ ๋ํ
์๋ ๋ฐฐ์น(batch) ์
๋ฐ์ดํธ๋ฅผ ์ ๊ฑฐํ์์ต๋๋ค.
๊ทธ๋ฆฌ๊ณ ์ด๋ฅผ ํตํด ์ฐ๋ฆฌ๋ ๋ณ๋์ ์ค์น๋ ๊ตฌ์ฑ ์์ด๋
๋ฐ๋ก ๋ ๋ง์ Batching์ ํตํด ์ฑ๋ฅ์ ๊ฐ์ ์ ๊ฒฝํํ ์ ์์ต๋๋ค.
๊ทธ๋ฐ๋ฐ,, ์ฌ๊ธฐ์ ๋งํ๋ ๋ฐฐ์นญ(Batching)์ด๋ ๋ญ๊น..?
Batching
function App() {
const [count, setCount] = useState(0);
const [isOpen, setIsOpen] = useState(false);
function handleClick() {
setCount(prev => prev + 1); // ์์ง ๋ฆฌ๋ ๋๋ง ๋์ง ์์
setIsOpen(prev => !prev); // ์์ง ๋ฆฌ๋ ๋๋ง ๋์ง ์์
// React๋ ๋ง์ง๋ง์ ํ ๋ฒ๋ง ๋ฆฌ๋ ๋๋งํ๋ค!
}
return (
<div>
<button onClick={handleClick}>Next</button>
<h1>{isOpen ? "์ด๋ฆผ" : "๋ซํ"}</h1>
{isOpen && <Toggle Component/>}
</div>
);
}
๐ฉโ๐ซ ์๋ฆฌ๋ฒ ์ดํฐ๊ฐ ํ ๋ช
์ด ํ๋ค๊ณ ๋ฐ๋ก ์ถ๋ฐํ๊ณ
๊ฐ ํ์น๊ฐ๋ง๋ค ๋์ผํ ์ด๋์ ๋ฐ๋ณตํ๋ ๊ฒ์ด ์๋๋ผ
ํ๋ ค๋ ์ฌ๋์ด ๋ชจ๋ ํ์นํ ์ดํ ์ถ๋ฐํ๋ ๋ชจ์ต์ ์์ํด๋ด
์๋ค!
function App() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
function handleClick() {
fetchSomething().then(() => {
// callback ์คํ ์ดํ์ ๋์ํ๋ ๋ถ๋ถ์ด๋ฏ๋ก
// React 17 ์ด์ ์ ๊ฒฝ์ฐ ์ด ๋ถ๋ถ์ Batching ๋์ง ์์
setCount(c => c + 1); // ๋ฆฌ๋ ๋๋ง ๋ฐ์
setFlag(f => !f); // ๋ฆฌ๋ ๋๋ง ๋ฐ์
});
}
return (
<div>
<button onClick={handleClick}>Next</button>
<h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
</div>
);
}
๐ฉโ๐ซ ์ด๋ฌํ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด Automatic Batching์ด ๋ฑ์ฅํ์ต๋๋ค!
addEventListener()
๊ณผ ๊ฐ์ ๊ธฐ์กด DOM ์ด๋ฒคํธ ๋ฆฌ์ค๋Promise
, setTimeout()
, setInterval()
๋ฑ)createRoot
์ ํจ๊ป ์์ํ๋ฉด, ์
๋ฐ์ดํธ๊ฐ ์ด๋์ ์์นํด์๋์ง์ ๊ด๊ณ ์์ด ๋ชจ๋ ์๋ Batching ๋๋ค.// ์ด์ : ์ค์ง React ์ด๋ฒคํธ์์๋ง Batching ์ ์ฉ
setTimeout(() => {
setCount(prev => prev + 1);
setFlag(prev => !prev);
// React๋ ๊ฐ๊ฐ์ ์ํ ์
๋ฐ์ดํธ๋ง๋ค ๋ ๋๋ง ๋๋ค. (์ด ๋ ๋ฒ)
}, 1000);
// ์ดํ: timeouts, promises ๋ฑ ๋ชจ๋ ์ด๋ฒคํธ ๋ด๋ถ์์ Batching ์ ์ฉ
setTimeout(() => {
setCount(prev => prev + 1);
setFlag(prev => !prev);
// React๋ ๋ง์ง๋ง์ ํ ๋ฒ๋ง ๋ฆฌ๋ ๋๋ง ๋๋ค.
}, 1000);
ํด๋ฆญ ์ ํ ๋ฒ์ ๋ ๋๋ง๋ง ๋ฐ์ํ๋ค.
createRoot
๋ฅผ ์ฌ์ฉํ์ง ์์ ๊ฒฝ์ฐ 17 ์ดํ ๋ฒ์ ๊ณผ ๋์ผํ๊ฒ ๋์ํ๋ ์ฃผ์ํ์!
๐ค ์์ ์ ๋์์ด ์์ง ๋จ์์๋ ์ด์ ๋ ๋ฌด์์ธ๊ฐ์?
๐ฉโ๐ซ ์ด์ ๋ฒ์ ๊ณผ 18 ๋ฒ์ ๋ชจ๋๋ฅผ ์ฌ์ฉํด ์ํ ์คํ์ ํ ๊ฒฝ์ฐ์ ๋๋นํด ์กด์ฌํฉ๋๋ค.
ํ์ง๋ง React 18 ์ฌ์ฉ ์ createRoot๋ฅผ ์ฌ์ฉํ์ฌ
automatic batching์ด ์ ์ฉ๋๋๋ก ํ๋ ๊ฒ์ด ์ข๊ฒ ์ต๋๋ค.
// (1) ํด๋ฆญ ์ด๋ฒคํธ
function handleClick() {
setCount(c => c + 1);
setFlag(f => !f);
// ๋งจ ๋ง์ง๋ง ๋ถ๋ถ์์๋ง ๋ฆฌ๋ ๋๋ง ๋ฐ์
}
// (2) setTimeout
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// ๋งจ ๋ง์ง๋ง ๋ถ๋ถ์์๋ง ๋ฆฌ๋ ๋๋ง ๋ฐ์
}, 1000);
// (3) Promise
fetch(/*...*/).then(() => {
setCount(c => c + 1);
setFlag(f => !f);
// ๋งจ ๋ง์ง๋ง ๋ถ๋ถ์์๋ง ๋ฆฌ๋ ๋๋ง ๋ฐ์
})
// (4) DOM ์ด๋ฒคํธ ๋ฆฌ์ค๋
elm.addEventListener('click', () => {
setCount(c => c + 1);
setFlag(f => !f);
// ๋งจ ๋ง์ง๋ง ๋ถ๋ถ์์๋ง ๋ฆฌ๋ ๋๋ง ๋ฐ์
});
import { flushSync } from 'react-dom';
function handleClick() {
flushSync(() => {
setCounter(c => c + 1);
});
// ์ด๋ React๊ฐ DOM์ ์
๋ฐ์ดํธ ํ๋ค.
flushSync(() => {
setFlag(f => !f);
});
// ์ด๋ React๊ฐ DOM์ ์
๋ฐ์ดํธ ํ๋ค.
}
๐ฉโ๐ซ ๊ทธ๋ ์ต๋๋ค!
๐ฉโ๐ซ ๋ฌธ์ ๊ฐ ์๊ธธ ์ ์๋ ์์ธ ์ํฉ์ด ์กด์ฌํฉ๋๋ค!
17์ดํ ๋ฒ์ ์์์ ํด๋์ค
ํด๋์ค์์๋ setState
ํธ์ถ ์ค this.state
๋ฅผ ํตํด ์ํ๊ฐ์ ๋๊ธฐ์ ์ผ๋ก ์ฝ์ ์ ์์๋ค.
// React 17 ์ดํ ๋ฒ์
handleClick = () => {
setTimeout(() => {
this.setState(({ count }) => ({ count: count + 1 }));
console.log(this.state); // { count: 1, isClicked: false }
this.setState(({ isClicked }) => ({ isClicked: !isClicked }));
});
};
18 ๋ฒ์ ์ ํด๋์ค
ํ์ง๋ง ์ด๋ React 18์์๋ ์ฌ์ฉํ ์ ์๋ ๋ฐฉ์์ด ๋์๋ค.
- setTimeout
๋ด๋ถ์ ์๋ ์
๋ฐ์ดํธ๋ค๊น์ง ๋ชจ๋ Batching๋๊ธฐ ๋๋ฌธ์ React๋ ๋์ด์ setState
์ ๊ฒฐ๊ณผ๋ฅผ ๋๊ธฐ์ ์ผ๋ก ๋ ๋๋งํ์ง ์๊ฒ ๋์๊ธฐ ๋๋ฌธ์ด๋ค!
// 18 ๋ฒ์
handleClick = () => {
setTimeout(() => {
this.setState(({ count }) => ({ count: count + 1 }));
console.log(this.state); // { count: 0, isClicked: false }
this.setState(({ isClicked }) => ({ isClicked: !isClicked }));
});
};
ReactDOM.flushSync
๋ฅผ ์ฌ์ฉํ์ฌ ์
๋ฐ์ดํธ๋ฅผ ๊ฐ์ ํ ์ ์๊ธด ํ๋ค.// 18 ๋ฒ์
handleClick = () => {
setTimeout(() => {
ReactDOM.flushSync(() => {
this.setState(({ count }) => ({ count: count + 1 }));
});
console.log(this.state); // { count: 1, isClicked: false }
this.setState(({ isClicked }) => ({ isClicked: !isClicked }));
});
};
Hooks๋ฅผ ๊ฐ์ง ํจ์ํ ์ปดํฌ๋ํธ๋ ์ด๋ฌํ ๋ฌธ์ ์ ์ํฅ์ ๋ฐ์ง ์๋๋ค.
- state ๋ณ๊ฒฝ์ด ๊ธฐ์กด ๊ฐ์ ์
๋ฐ์ดํธํ์ง ์๊ธฐ ๋๋ฌธ์ด๋ค!
function handleClick() {
setTimeout(() => {
console.log(count); // 0
setCount(c => c + 1);
setCount(c => c + 1);
setCount(c => c + 1);
console.log(count); // 0
}, 1000)
setCount
ํจ์๋ ์ด์ ์ํ ๊ฐ์ ์
๋ฐ์ดํธํ๋ ํจ์๋ฅผ ์ธ์๋ก ๋ฐ๊ณ , ์ด์ ์ํ ๊ฐ์ ์ง์ ์ฐธ์กฐํ์ฌ ์๋ก์ด ์ํ ๊ฐ์ ๊ณ์ฐํ๋ ํจ์ํ ์
๋ฐ์ดํธ๋ฅผ ์ํํ๋ค.
- ํ์ง๋ง ์ด๋ฌํ ์ํ ์
๋ฐ์ดํธ ํจ์๋ ๋น๋๊ธฐ์ ์ผ๋ก ๋์ํ๋ค.
setCount
ํจ์ ํธ์ถ ์ ๊ธฐ์กด ์ํ ๊ฐ์ด ์ฆ์ ์
๋ฐ์ดํธ๋๋ ๊ฒ์ด ์๋๋ผ ์ด๋ฒคํธ ๋ฃจํ๋ฅผ ํตํด ๋๊ธฐํ๋ค๊ฐ ๋ค์ ๋ ๋๋ง ์ฌ์ดํด์์ ์
๋ฐ์ดํธ ๋๋ค.setTimeout
ํจ์ ๋ด๋ถ์ ์
๋ฐ์ดํธ๋ค์ ์์ง ๋ ๋๋ง ์ฌ์ดํด์์ ์ฒ๋ฆฌ๋์ง ์์ ๋ ๋ฒ์งธ ์ฝ์์๋ ์ด์ ์ํ ๊ฐ์ธ '0'์ด ์ถ๋ ฅ๋๋ค.์์ ์ฝ๋์์ setTimeout
ํจ์ ์ข
๋ฃ ํ ์
๋ฐ์ดํธ ๋ ๊ฐ์ ํ์ธํ๊ณ ์ถ๋ค๋ฉด ์์ ๋ฐฉ์์ผ๋ก ์ฝ๋ฐฑ ํจ์ ๋ด์์ ์ง์ ์ํ ๊ฐ์ ์ฐธ์กฐํ๋ ๊ฒ์ด ์๋๋ผ, useEffect
ํ
์ ์ฌ์ฉํ์ฌ ๊ฐ์ด ๋ณ๊ฒฝ๋ ๋๋ง๋ค ์ฝ์์ ๊ฐ์ด ์ฐํ๋ ๋ฐฉ์์ผ๋ก ์์ ํด์ผ ํ ๊ฒ์ด๋ค.
setState
๋ฅผ ๊ฐ์ ํ๊ธฐ ์ํด ์๋์ ๊ฐ์ ๋ฌธ์ํ๋์ง๋ ์์ API๋ฅผ ์ฌ์ฉํ๊ธฐ๋ ํ๋ค.
unstable_batchedUpdates
import { unstable_batchedUpdates } from 'react-dom';
const batchUpdate = unstable_batchedUpdates(() => {
setCount((prev) => (prev) + 1);
setFlag((prev) => !(prev));
});
batchUpdate()