React Compiler, ์ด๋ป๊ฒ ๋์ํ ๊น [1] ์ง๋ ๊ธ์์ React Compiler์ ์ ์ฒด์ ์ธ ๊ตฌ์กฐ๋ฅผ ์ดํด๋ณด์๋ค.
์ปดํ์ผ๋ฌ์ ๋์ ๋ฐฉ์์ ์ดํด๋ณด๊ธฐ์ ์, ๋ง์ด ์ธ๊ธ๋์๋ useMemoCache
๋ฅผ ๋จผ์ ์ดํด๋ณด๊ณ ๊ฐ์.
useMemoCache implementation by josephsavona ยท Pull Request #25143 ยท facebook/react ยท GitHub
Summary context: I'm still a noob to this codebase so this started as a hacky implementation to get feedback. Initial implementation of useMemoCache that aims to at least get the basics correct, ev...
https://github.com/facebook/react/pull/25143useMemoCache
์ ๋ํ ์ด๊ธฐ ๊ตฌํ PR์ด๋ค. ๊ถ๊ธํ๋ค๋ฉด ํ๋ฒ ์ดํด๋ณด์.
์ผ๋จ ์ ์ฒด ์ฝ๋๋ฅผ ์ญ ํ์ด ๋ณด๊ณ ๋ถ๋ถ๋ถ๋ถ ์ดํด๋ณด์
// react-reconciler/src/ReactFiberHooks.js
function useMemoCache(size: number): Array<any> {
let memoCache = null;
// Fast-path, load memo cache from wip fiber if already prepared
let updateQueue: FunctionComponentUpdateQueue | null =
(currentlyRenderingFiber.updateQueue: any);
if (updateQueue !== null) {
memoCache = updateQueue.memoCache;
}
// Otherwise clone from the current fiber
if (memoCache == null) {
const current: Fiber | null = currentlyRenderingFiber.alternate;
if (current !== null) {
const currentUpdateQueue: FunctionComponentUpdateQueue | null =
(current.updateQueue: any);
if (currentUpdateQueue !== null) {
const currentMemoCache: ?MemoCache = currentUpdateQueue.memoCache;
if (currentMemoCache != null) {
memoCache = {
// When enableNoCloningMemoCache is enabled, instead of treating the
// cache as copy-on-write, like we do with fibers, we share the same
// cache instance across all render attempts, even if the component
// is interrupted before it commits.
//
// If an update is interrupted, either because it suspended or
// because of another update, we can reuse the memoized computations
// from the previous attempt. We can do this because the React
// Compiler performs atomic writes to the memo cache, i.e. it will
// not record the inputs to a memoization without also recording its
// output.
//
// This gives us a form of "resuming" within components and hooks.
//
// This only works when updating a component that already mounted.
// It has no impact during initial render, because the memo cache is
// stored on the fiber, and since we have not implemented resuming
// for fibers, it's always a fresh memo cache, anyway.
//
// However, this alone is pretty useful โ it happens whenever you
// update the UI with fresh data after a mutation/action, which is
// extremely common in a Suspense-driven (e.g. RSC or Relay) app.
data: enableNoCloningMemoCache
? currentMemoCache.data
: // Clone the memo cache before each render (copy-on-write)
currentMemoCache.data.map(array => array.slice()),
index: 0,
};
}
}
}
}
// Finally fall back to allocating a fresh instance of the cache
if (memoCache == null) {
memoCache = {
data: [],
index: 0,
};
}
if (updateQueue === null) {
updateQueue = createFunctionComponentUpdateQueue();
currentlyRenderingFiber.updateQueue = updateQueue;
}
updateQueue.memoCache = memoCache;
let data = memoCache.data[memoCache.index];
if (data === undefined) {
data = memoCache.data[memoCache.index] = new Array(size);
for (let i = 0; i < size; i++) {
data[i] = REACT_MEMO_CACHE_SENTINEL;
}
} else if (data.length !== size) {
// TODO: consider warning or throwing here
if (__DEV__) {
console.error(
'Expected a constant size argument for each invocation of useMemoCache. ' +
'The previous cache was allocated with size %s but size %s was requested.',
data.length,
size,
);
}
}
memoCache.index++;
return data;
}
์ด์ ํ๋ฒ ์์๋๋ก ํ ์ ์ดํด๋ณด์.
๋น ๋ฅธ ๊ฒฝ๋ก
์ฒ์ ์ด๋ค์ง๋ ์์
์ ํ์ฌ ๋ ๋๋ง ์ค์ธ ํ์ด๋ฒ์ updateQueue
์์ memoCache
๋ฅผ ์ฐพ์์ค๋ ์์
์ด๋ค.
์ด์ ์ ํธ์ถ๋ ์ (๋ ๋๋ง๋ ์ )์ด ์๋ค๋ฉด ์กด์ฌํ ๊ฒ์ด๋ค. ์ด๋ฅผ ํตํด ๋ถํ์ํ ์บ์ ํ ๋น์ ๋ฐฉ์งํ ์ ์๋ค.
let memoCache = null;
// Fast-path, load memo cache from wip fiber if already prepared
let updateQueue: FunctionComponentUpdateQueue | null =
(currentlyRenderingFiber.updateQueue: any);
if (updateQueue !== null) {
memoCache = updateQueue.memoCache;
}
์บ์๊ฐ ์๋ค๋ฉด? ๋์ฒด ํ์ด๋ฒ (Alternate fiber)์์ ์บ์ ๋ณต์
ํ์ฌ ๋ ๋๋ง ์ค์ธ fiber์ updateQueue
์ memoCache
๊ฐ ์๋ค๋ฉด, ๋์ฒด ํ์ด๋ฒ(Alternate fiber)๋ฅผ ํ์ธํ๋ค.
React์ Fiber ์ํคํ
์ฒ์์๋ ๊ฐ๊ฐ์ Fiber ๋
ธ๋๊ฐ โcurrentโ ํธ๋ฆฌ์ โworkInProgressโ ํธ๋ฆฌ ์ค ํ๋์ ์ํ๊ฒ ๋๋ค.
โcurrentโ ํธ๋ฆฌ๋ ํ์ฌ ํ๋ฉด์ ๋ ๋๋ง๋์ด ์๋ ์ปดํฌ๋ํธ์ ์ํ๋ฅผ ๋ํ๋ด๊ณ , โworkInProgressโ ํธ๋ฆฌ๋ React๊ฐ ์
๋ฐ์ดํธ๋ฅผ ์ ์ฉํ๋ ค๊ณ ํ๋ ์ํ๋ฅผ ๋ํ๋ธ๋ค.
์ฌ๊ธฐ์ ๋งํ๋ ๋์ฒด ํ์ด๋ฒ(Alternate Fiber)๋ currentlyRenderingFiber
, ์ฆ workInProgress
ํธ๋ฆฌ์ alternate ์ด๋ฏ๋ก, current ํธ๋ฆฌ์ Fiber๋ฅผ ๊ฐ๋ฆฌํจ๋ค.\
์ด ๋์ฒด ํ์ด๋ฒ(current)์์ ์บ์๋ฅผ ๋ณต์ ํ์ฌ ์ฌ์ฉํ๋ค.
๋ก์ง์ ํ๋ฆ์ ๋ณด๊ธฐ ์ํด ์กฐ๊ฑด๋ฌธ์ ์ ์ ์ ๊ฑฐ ํด๋์๋ค. ์๋ฌธ์ ์์์ ํ์ธํ ์ ์๋ค.
let memoCache = null;
// ...
// ์บ์๊ฐ ์๋ค๋ฉด, ๋์ฒด ํ์ด๋ฒ์์ ์บ์ ๋ณต์
if (memoCache == null) {
const current = currentlyRenderingFiber.alternate; // ๋์ฒด ํ์ด๋ฒ(current)๋ฅผ ๊ฐ์ ธ์จ๋ค.
const currentUpdateQueue = current.updateQueue; // ๋์ฒด ํ์ด๋ฒ์ updateQueue๋ฅผ ๊ฐ์ ธ์จ๋ค.
const currentMemoCache: ?MemoCache = currentUpdateQueue.memoCache; // ๋์ฒด ํ์ด๋ฒ์ memoCache๋ฅผ ๊ฐ์ ธ์จ๋ค.
memoCache = {
data: enableNoCloningMemoCache
? currentMemoCache.data
: // Clone the memo cache before each render (copy-on-write)
currentMemoCache.data.map(array => array.slice()),
index: 0,
};
}
์ด ๋ ์บ์๋ฅผ ๋ณต์ฌํ๋ ๊ณผ์ ์์ enableNoCloningMemoCache
์ต์
์ ๋ฐ๋ผ ๋์์ด ๋ฌ๋ผ์ง๋ค.
enableNoCloningMemoCache
ํ๋๊ทธ์ ๋ฐ๋ผ ์บ์ ๋ฐ์ดํฐ๋ฅผ ๊ทธ๋๋ก ์ฌ์ฉํ ์ง, ์์ ๋ณต์ฌ๋ฅผ ํ ์ง ๊ฒฐ์ ํ๋ค.
- ํ์ฑํ๋์ด ์๋ค๋ฉด, ์บ์ ๋ฐ์ดํฐ๋ฅผ ๊ทธ๋๋ก ์ฌ์ฉํ๋ค. ์ด๋ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ ์ค์ผ ์ ์์ง๋ง, ์บ์ ๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋ ๋ฆฌ์คํฌ๋ฅผ ๊ฐ์ง๊ณ ์๋ค.
- ๋นํ์ฑํ๋์ด ์๋ค๋ฉด, ์บ์ ๋ฐ์ดํฐ๋ฅผ ์์ ๋ณต์ฌํ์ฌ ์ฌ์ฉํ๋ค. ์ด๋ ์ด์ ๋ ๋๋ง์ ์บ์ ๋ฐ์ดํฐ๋ฅผ ์์ ํ๊ฒ ์ฌ์ฉํ ์ ์๊ฒ ํด์ฃผ์ง๋ง, ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ด ์ฆ๊ฐํ ์ ์๋ค.
[Experiment] Reuse memo cache after interruption by acdlite ยท Pull Request #28878 ยท facebook/react ยท GitHub
Adds an experimental feature flag to the implementation of useMemoCache, the internal cache used by the React Compiler (Forget). When enabled, instead of treating the cache as copy-on-write, like w...
https://github.com/facebook/react/pull/28878์ด๋ถ๋ถ์ ๊ธด ์ฃผ์์ด ๋ฌ๋ ค์๋๋ฐ, ์ด ๋ด์ฉ๋ ์ดํด๋ณด์. ๋ฒ์ญ์ ๋ฏธ๋ฆฌ ํด๋ณด์๋ค.
`enableNoCloningMemoCache`๊ฐ ํ์ฑํ๋๋ฉด, ํ์ด๋ฒ์์์ฒ๋ผ ์บ์๋ฅผ copy-on-write๋ก ์ฒ๋ฆฌํ์ง ์๊ณ ,
์ฌ์ง์ด ์ปดํฌ๋ํธ๊ฐ ์ปค๋ฐ๋๊ธฐ ์ ์ ์ค๋จ๋๋๋ผ๋ ๋ชจ๋ ๋ ๋๋ง ์๋์์ ๋ชจ๋ ๋์ผํ ์บ์ ์ธ์คํด์ค๋ฅผ ๊ณต์ ํฉ๋๋ค.
๋ง์ฝ ์
๋ฐ์ดํธ๊ฐ ์ค๋จ๋๋ฉด, ์ผ์ ์ค๋จ๋์๊ธฐ ๋๋ฌธ์ด๋ ๋ค๋ฅธ ์
๋ฐ์ดํธ ๋๋ฌธ์ด๋ ,
์ด์ ์๋์์ ๋ฉ๋ชจ๋ ๊ณ์ฐ์ ์ฌ์ฌ์ฉํ ์ ์์ต๋๋ค.
React ์ปดํ์ผ๋ฌ๊ฐ ๋ฉ๋ชจ ์บ์์ ๋ํด ์์์ ์ฐ๊ธฐ๋ฅผ ์ํํ๊ธฐ ๋๋ฌธ์ ์ด๊ฒ์ด ๊ฐ๋ฅํฉ๋๋ค.
์ฆ, ๋ฉ๋ชจ์ด์ ์ด์
์ ์ถ๋ ฅ์ ๊ธฐ๋กํ์ง ์๊ณ ๋ ์
๋ ฅ์ ๊ธฐ๋กํ์ง ์์ต๋๋ค.
์ด๊ฒ์ ์ปดํฌ๋ํธ์ ํ
๋ด์์ ์ผ์ข
์ "์ฌ๊ฐ(resuming)" ํํ๋ฅผ ์ ๊ณตํฉ๋๋ค.
์ด๊ฒ์ ์ด๋ฏธ ๋ง์ดํธ๋ ์ปดํฌ๋ํธ๋ฅผ ์
๋ฐ์ดํธํ ๋๋ง ์๋ํฉ๋๋ค. ๋ฉ๋ชจ ์บ์๊ฐ ํ์ด๋ฒ์ ์ ์ฅ๋๊ณ ,
ํ์ด๋ฒ์ ๋ํ ์ฌ๊ฐ๋ฅผ ๊ตฌํํ์ง ์์๊ธฐ ๋๋ฌธ์ ์ด๊ธฐ ๋ ๋๋ง ๋์์๋ ์ํฅ์ ๋ฏธ์น์ง ์์ต๋๋ค.
์ด์จ๋ ํญ์ ์๋ก์ด ๋ฉ๋ชจ ์บ์์
๋๋ค.
๊ทธ๋ฌ๋ ์ด๊ฒ๋ง์ผ๋ก๋ ๊ฝค ์ ์ฉํฉ๋๋ค. ๋ณ์ด/์ก์
ํ์ ์๋ก์ด ๋ฐ์ดํฐ๋ก UI๋ฅผ ์
๋ฐ์ดํธํ ๋๋ง๋ค ๋ฐ์ํ๋๋ฐ,
์ด๋ Suspense ๊ธฐ๋ฐ(์: RSC ๋๋ Relay)์ ์ฑ์์ ๋งค์ฐ ์ผ๋ฐ์ ์
๋๋ค.
React ์ปดํ์ผ๋ฌ๊ฐ ๋ฉ๋ชจ ์บ์์ ๋ํด ์์์ ์ฐ๊ธฐ๋ฅผ ์ํํ๊ธฐ ๋๋ฌธ์ ์ด๊ฒ์ด ๊ฐ๋ฅํฉ๋๋ค. ์ฆ, ๋ฉ๋ชจ์ด์ ์ด์
์ ์ถ๋ ฅ์ ๊ธฐ๋กํ์ง ์๊ณ ๋ ์
๋ ฅ์ ๊ธฐ๋กํ์ง ์์ต๋๋ค.
์ด ์๋ฏธ๋ ๋ญ๊น?
๐คทโโ๏ธ ์์๊ฐ ์ฌ๊ธฐ์ ์ ๋์?
์ฐ์ โ์์์ (Atomic)โ ์ด๋ผ๋ ๋จ์ด๋ โ๋ ์ด์ ๋๋ ์ ์๋โ๋ผ๋ ์๋ฏธ๋ก ์ฌ์ฉ๋๋ค. (์์๋ ๋๋ ์ ์๋๋ฐ์? ๋ผ๋ ๋ฐ์ธ์ ํ์ง๋ง์)
์ปดํจํฐ ๊ณผํ์์์ โ์์์ ์์
(Atomic operation)โ์ โ๋ ์ด์ ๋๋ ์ ์๋ ์์
โ, ์ฆ ํ ๋ฒ์ ์์ ํ ์คํ๋๊ฑฐ๋ ์คํ๋์ง ์๋ ์์
์ ์๋ฏธํ๋ค.
์์๋ฅผ ๋ค์ด๋ณด์.
์ํ์์์ ๊ณ์ข์ด์ฒด๋ฅผ ๋ ์ฌ๋ ค๋ณด์. ๋ง์ฝ A ๊ณ์ข์์ B ๊ณ์ข๋ก 10๋ง์์ ์ด์ฒดํ๋ค๊ณ ํ ๋ ์ด ์์
์ ๋๋จ๊ณ๋ก ์ด๋ค์ง๊ฒ ๋๋ค.
- A ๊ณ์ข์์ 10๋ง์์ ๋นผ๋ ์์
- B ๊ณ์ข์ 10๋ง์์ ๋ํ๋ ์์
๋ง์ฝ ์ด ๊ณผ์ ์ฌ์ด์ ๋ฌธ์ ๊ฐ ์๊ฒจ์ ํ๋ก์ธ์ค๊ฐ ์ค๋จ๋๋ค๋ฉด ์ด๋ป๊ฒ ๋ ๊น?
A ๊ณ์ข์์๋ 10๋ง์์ด ์ฌ๋ผ์ก์ง๋ง, B ๊ณ์ข๋ ๋ฐ์ง ๋ชปํ ์ํ๊ฐ ๋์ด๋ฒ๋ฆฐ๋ค. ์ด๋ฐ ์ํฉ์ ๋ฐฉ์งํ๊ธฐ ์ํด โ์์์ ์์
โ์ด ํ์ํ๋ค.
์ฆ ์ด์ฒด ์์
์ ํ๋ฒ์ ์์ ํ ์คํ๋๊ฑฐ๋ ์คํ๋์ง ์์์ผ ํ๋ค.
React Compiler์ ๋ฉ๋ชจ์ด์ ์ด์ ๊ณผ์ ๋ ์ด์ ๋น์ทํ ๊ฐ๋ ์ ์ฌ์ฉํ๋ค. ์์๋ฅผ ํตํด์ ์ค๋ช ํด๋ณด์.
์๋ ์ฝ๋๋ฅผ ์ปดํ์ผ ํด๋ณด์.
function Component({ active }) {
let color: string;
if (active) {
color = "red";
} else {
color = "blue";
}
return <div styles={{ color }}>hello world</div>;
}
์ปดํ์ผ ํ๊ฒ๋๋ฉด ์๋์ ๊ฐ์ด ๋ณํ๋๋ค.
function Component(t0) {
const $ = _c(2);
const { active } = t0;
let color;
if (active) {
color = "red";
} else {
color = "blue";
}
let t1;
if ($[0] !== color) {
t1 = (
<div styles={{ color }}>hello world</div>
);
$[0] = color;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
์ฌ๋ฆฌ์ ์ฐ๋ฆฌ๊ฐ ์ฃผ๋ชฉํด์ผํ ๋ถ๋ถ์ ์ด๋ถ๋ถ์ด๋ค.
if ($[0] !== color) {
t1 = (
<div styles={{ color }}>hello world</div>
);
$[0] = color;
$[1] = t1;
} else {
t1 = $[1];
}
๋จผ์ ๊ฐ๋จํ ๋์์ ์ค๋ช
ํด๋ณด์.
$
๋ memoCache๋ฅผ ์๋ฏธํ๋ค. ์ปดํ์ผ๋ฌ๋ $[0]
์๋ ์ด์ ์ ์ ์ฅ๋ color ๊ฐ์ ์ ์ฅํ๊ณ , $[1]
์๋ ์ด์ ์ ๋ ๋๋ง๋ ๊ฒฐ๊ณผ๋ฅผ ์ ์ฅํ๊ณ ์๋ค.
if ($[0] !== color)
์บ์๋ color๊ฐ๊ณผ ํ์ฌ color๊ฐ์ ๋น๊ตํ๋ค. ๋ง์ฝ ๋ค๋ฅด๋ค๋ฉด, ์๋ก์ด color์ ๋ํ ์๋ฆฌ๋จผํธ๋ฅผ ์์ฑํด์ผํ๋ค๋ ์๋ฏธ์ด๋ค.
์ด๋ ์๋ก์ด ์๋ฆฌ๋จผํธ๋ฅผ ์์ฑํ๊ณ , $[0]
์ ์๋ก์ด color๊ฐ์ ์ ์ฅํ๊ณ , $[1]
์ ์๋ก์ด ์๋ฆฌ๋จผํธ๋ฅผ ์ ์ฅํ๋ค.
๊ทธ๋ ์ง ์๋ค๋ฉด, ์ด์ ์ ๋ ๋๋ง๋ ๊ฒฐ๊ณผ๋ฅผ ์ฌ์ฉํ๋ค.
์ฌ๊ธฐ์ ์ค์ํ ์ ์ $[0] = color;
์ $[1] = t1;
์ด ๊ฐ์ ๋ธ๋ก ๋ด์์ ์ฐ์์ ์ผ๋ก ์ผ์ด๋๋ค๋ ๊ฒ์ด๋ค.
์ด๊ฒ์ด ๋ฐ๋ก โ์์์ ์ฐ๊ธฐโ์ด๋ค.
์ด ๋ ์ค์ ํ๋์ ์์์ ์ฐ์ฐ์ผ๋ก ์ฒ๋ฆฌ๋๊ธฐ์, color ๊ฐ์ด ์บ์์ ์ฐ์ด๋ฉด, ๊ทธ์ ํด๋นํ๋ ์๋ฆฌ๋จผํธ๋ ๋ฐ๋์ ์บ์์ ์ฐ์ธ๋ค. ์ด๋ค ์ฌ์ด์ ์ค๊ฐ ์ํ๋ ์กด์ฌํ์ง ์๋๋ค.
์ด์ React Compiler์ ์์์ ์ฐ๊ธฐ๊ฐ ๋ฌด์์ ์๋ฏธํ๋์ง ์๊ฒ ๋์๋ค.
๋ค์ ๋ณธ๋ ๋ด์ฉ์ผ๋ก ๋์๊ฐ ๋ณด์.
enableNoCloningMemoCache
๊ฐ false ๋ผ๋ฉด ๊ฐ ๋ ๋๋ง ์๋์ ์ด์ ๋ ๋๋ง์ ์บ์๋ฅผ ๋ณต์ฌํ๊ณ ์ด ๋ณต์ฌ๋ณธ์ ์์ ํ๋ ์์ผ๋ก (copy-on-write) ์ฌ์ฉํ๋ค.
ํ์ง๋ง ์ด ๋ฐฉ๋ฒ์ ๋ ๋๋ง์ด ์ค๋จ๋๊ณ ์ฌ๊ฐ๋ ๋๋ง๋ค ์บ์๋ฅผ ๋ณต์ฌํ๋ฏ๋ก, ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ด ์ฆ๊ฐํ ์ ์๋ค.
enableNoCloningMemoCache
๊ฐ true๋ผ๋ฉด ๋ชจ๋ ๋ ๋๋ง ์๋์์ ๋์ผํ ์บ์ ์ธ์คํด์ค๋ฅผ ๊ณต์ ํ๋ค.
์ฆ, ๋ ๋๋ง์ด ์ค๋จ(suspended/interrupted)๋๊ณ ์ฌ๊ฐ ๋๋๋ผ๋, ์ด์ ๋ ๋๋ง ์๋์์์ ์บ์๋ฅผ ๊ทธ๋๋ก ์ฌ์ฉํ ์ ์๋ค.
์ด๋ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ์ ์์ด์ ํฐ ์ด์ ์ด๋ค.
์ด๋ฐ ๋์์ด ๊ฐ๋ฅํ๋ ค๋ฉด React ์ปดํ์ผ๋ฌ๊ฐ ๋ฉ๋ชจ ์บ์์ ๋ํด ์์์ ์ฐ๊ธฐ๋ฅผ ์ํํด์ผ ํ๋ค๋ ๊ฒ์ด๋ค.
์๋ฅผ ๋ค์ด ๋ณด์ฅ๋์ง ์์๋ค๋ฉด, ์๋์ ๊ฐ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ ๊ฒ์ด๋ค.
- โAโ๋ ๋๋ง ์๋๊ฐ color ๊ฐ์ โredโ๋ก ์บ์์ ์ ์ฅํ๋ค.
- ๊ทธ๋ฌ๋ ์๋ฆฌ๋จผํธ๋ฅผ ์บ์์ ๊ธฐ๋กํ๊ธฐ ์ ์, ๋ ๋๋ง์ด ์ค๋จ๋์ด๋ฒ๋ฆฐ๋ค.
- โBโ๋ ๋๋ง ์๋๊ฐ ์์๋๋ฉด, ์ด๋ ์บ์์๋ color์ ๋ํ ์ ๋ ฅ์ ์์ง๋ง, ์ถ๋ ฅ์ด ์๋ ์ํ๊ฐ ๋๋ค.
์ด๋ ์บ์ ๋ถ์ผ์น ๋ฌธ์ ๋ฅผ ๋ฐ์์ํค๊ณ ์ด๋ ๋ ๋๋ง ๊ฒฐ๊ณผ์ ๋ถ์ผ์น๋ก ์ด์ด์ง ๊ฒ์ด๋ค.
์ํ์ ๋ฐ๋ฅธ ๋ ๋๋ง ๋ถ์ผ์น ๋ฌธ์ โฆ ์ด๋์ ๋ค์ด๋ณธ์ ์ด ์์ง ์๋์? โuseSyncExternalStoreโ๋ฅผ ์ค๋ช
ํ ๋ ๋์ค๋ tearing ๋ฌธ์ ์ ๋น์ทํ๋ค.
What is tearing? ยท reactwg/react-18 ยท Discussion #69 ยท GitHub
What is tearing?
https://github.com/reactwg/react-18/discussions/69ํ์ง๋ง โ์์์ ์ฐ๊ธฐโ๊ฐ ๋ณด์ฅ๋๋ค๋ฉด, color์ ๋ํ ์บ์๊ฐ ์๋ค๋ ๊ฒ์ ๋ฐ๋์ ์๋ฆฌ๋จผํธ์ ๋ํ ์บ์๋ ์๋ค๋ ๊ฒ์ ๋ณด์ฅ ๋ฐ์ ์ ์๋ค.
ํด, ๋ญ๊ฐ ๊ฝค๋ ๋ฉ๋ฆฌ ์๋ค. ์ด์ ๋ค์ useMemoCache
๋ก ๋์๊ฐ๋ณด์.
enableNoCloningMemoCache
์กฐ๊ฑด์ ๋ฐ๋ฅธ ๋ถ๊ธฐ๋ฅผ ๋ณด๋ค๊ฐ ์ฌ๊ธฐ๊น์ง ์๋ค.
๊ทธ๋์ enableNoCloningMemoCache
๋ ์ฐธ์ด๋ ๊ฑฐ์ง์ด๋!
// react/shared/ReactFeatureFlags.js
// Test this at Meta before enabling.
export const enableNoCloningMemoCache = false;
ํ ์์ ์์๋ enableNoCloningMemoCache
๋ false๋ก ์ค์ ๋์ด ์๋ค.
๋ค์ useMemoCache
๋ก ๋์๊ฐ๋ณด์.
์บ์๊ฐ ์๋ค๋ฉด? ์๋ก์ด ์บ์ ์์ฑ
์ด์ ๋ง์ง๋ง์ผ๋ก ์บ์๊ฐ ์๋ค๋ฉด, ์๋ก์ด ์บ์๋ฅผ ์์ฑํ๋ค.
if (memoCache == null) {
memoCache = {
data: [],
index: 0,
};
}
์บ์ ํ ๋น
updateQueue๊ฐ ์๋ค๋ฉด ์๋ก ๋ง๋ค๊ณ , memoCache๋ฅผ ํ ๋นํ๋ค.
if (updateQueue === null) {
updateQueue = createFunctionComponentUpdateQueue();
currentlyRenderingFiber.updateQueue = updateQueue;
}
updateQueue.memoCache = memoCache;
createFunctionComponentUpdateQueue
๋ ๊ธฐ๋ณธ์ ์ธ queue๊ฐ์ฒด๋ฅผ return ํ๋ค.
enableUseMemoCacheHook ํ๋๊ทธ์ ๋ฐ๋ผ ๋๋๋๋ฐ ํ์ฌ๋ true๋์ด์๋ค.
// NOTE: defining two versions of this function to avoid size impact when this feature is disabled.
// Previously this function was inlined, the additional `memoCache` property makes it not inlined.
// ๋ฉ๋ชจ : ์ด ๊ธฐ๋ฅ์ด ๋นํ์ฑํ๋์ด ์์ ๋ ํฌ๊ธฐ ์ํฅ์ ํผํ๊ธฐ ์ํด ์ด ํจ์์ ๋ ๊ฐ์ง ๋ฒ์ ์ ์ ์ํฉ๋๋ค.
// ์ด์ ์ ์ด ํจ์๋ ์ธ๋ผ์ธ์ผ๋ก ์ ์๋์์ผ๋ฉฐ, ์ถ๊ฐ์ ์ธ `memoCache` ์์ฑ์ผ๋ก ์ธํด ์ธ๋ผ์ธ๋์ง ์์ต๋๋ค.
let createFunctionComponentUpdateQueue: () => FunctionComponentUpdateQueue;
if (enableUseMemoCacheHook) {
createFunctionComponentUpdateQueue = () => {
return {
lastEffect: null,
events: null,
stores: null,
memoCache: null,
};
};
} else {
createFunctionComponentUpdateQueue = () => {
return {
lastEffect: null,
events: null,
stores: null,
};
};
}
์บ์ ๋ฐ์ดํฐ ๋ฐํ
๋ง์ง๋ง์ผ๋ก ์บ์ ๋ฐ์ดํฐ๋ฅผ ๋ฐํํ๋ค.
์บ์๋ก ๋ถํฐ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ณ ,
let data = memoCache.data[memoCache.index];
๋ฐ์ดํฐ๊ฐ ์๋ค๋ฉด ์๋ก์ด ๋ฐ์ดํฐ๋ฅผ ํ ๋นํ๊ณ , REACT_MEMO_CACHE_SENTINEL
๋ก ์ด๊ธฐํํ๋ค.
REACT_MEMO_CACHE_SENTINEL
์ memoization์ด ๋์ง ์์ ์ํ๋ฅผ ๋ํ๋ธ๋ค.
ํธ์ถ ๋ณ๋ก ์บ์๋ Arrayํํ๋ก ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ Array๋ก ์ด๊ธฐํํ๋ค.
if (data === undefined) {
data = memoCache.data[memoCache.index] = new Array(size);
for (let i = 0; i < size; i++) {
data[i] = REACT_MEMO_CACHE_SENTINEL;
}
}
๋ง์ฝ data๊ฐ undefined
๊ฐ ์๋๋ผ๋ฉด, ์ฆ ์บ์ ๋ฐ์ดํฐ๊ฐ ์กด์ฌํ๋ค๋ฉด, data์ ๊ธธ์ด๊ฐ ์์ฒญ๋ size์ ์ผ์นํ๋์ง ํ์ธํ๋ค.
ํ์ธํ๋ ์ด์ ๋, ์ด์ ๋ ๋๋ง์์ ์ฌ์ฉ๋ ์บ์ ๋ฐ์ดํฐ์ ํ์ฌ ๋ ๋๋ง์์ ์ฌ์ฉํ ์บ์ ๋ฐ์ดํฐ์ ๊ธธ์ด๊ฐ ๋ค๋ฅด๋ค๋ฉด, ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์๊ธฐ ๋๋ฌธ์ด๋ค.
else if (data.length !== size) {
if (__DEV__) {
console.error(
'useMemoCache์ ๊ฐ ํธ์ถ์ ๋ํด ์์ ํฌ๊ธฐ ์ธ์๊ฐ ์์๋ฉ๋๋ค.' +
'์ด์ ์บ์๋ ํฌ๊ธฐ %s๋ก ํ ๋น๋์์ง๋ง ํฌ๊ธฐ %s๊ฐ ์์ฒญ๋์์ต๋๋ค.',
data.length,
size,
);
}
}
๋ง์ง๋ง์ผ๋ก ์บ์ ์ธ๋ฑ์ค๋ฅผ ์ฆ๊ฐ์ํค๊ณ ๋ฐ์ดํฐ๋ฅผ ๋ฐํํ๋ค.
memoCache.index++;
return data;
์ด์ ํธ์์ ์ดํด๋ดค๋ โreact-compiler-runtimeโ์์์ ๊ตฌํ๊ณผ ๋น์ทํ์ง๋ง, ์กฐ๊ธ๋ Fiber์ ๋ง๊ฒ ์์ ๋ ๋ถ๋ถ์ด ์๋ค.
useMemo
์ useMemoCache
์ฐจ์ด
์ฐ๋ฆฌ์๊ฒ ์ต์ํ, ๊ทธ๋ฆฌ๊ณ ์ด์ ์ํ์ง๊ฒ ๋ useMemo
. ์ด๋ฆ๋ ๋น์ทํ ํ์ ์ฐจ์ด์ ๋ํ ์๋ฌธ์ ๊ฐ์ง ์ ์์ ๊ฒ์ด๋ค.
๊ฐ๋จํ๊ฒ useMemo
์ ๊ตฌํ์ ๋์๋ณด์.
function mountMemo<T>(
nextCreate: () => T,
deps: Array<mixed> | void | null,
): T {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const nextValue = nextCreate();
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
function updateMemo<T>(
nextCreate: () => T,
deps: Array<mixed> | void | null,
): T {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps; //
const prevState = hook.memoizedState;
if (prevState !== null) {
// Assume these are defined. If they're not, areHookInputsEqual will warn.
if (nextDeps !== null) {
const prevDeps: Array<mixed> | null = prevState[1];
if (areHookInputsEqual(nextDeps, prevDeps)) { // ์ด์ deps์ ํ์ฌ deps๊ฐ ๊ฐ๋ค๋ฉด ์ด์ ๊ฐ์ ๋ฐํ
return prevState[0];
}
}
}
const nextValue = nextCreate(); // ์๋ก์ด ๊ฐ ์์ฑ
hook.memoizedState = [nextValue, nextDeps]; // ์๋ก์ด ๊ฐ๊ณผ deps๋ฅผ ์ ์ฅ
return nextValue; // ์๋ก์ด ๊ฐ ๋ฐํ
}
๋๋ค ๋ฉ๋ชจ์ด์ ์ด์
์ ์ํ ํ
์ด์ง๋ง ์กฐ๊ธ ๋ค๋ฅด๋ค, useMemo
๋ ๊ฐ๋ฐ์๊ฐ ์ง์ ์ ์ผ๋ก ์ฌ์ฉํ๋ ํ
์ผ๋ก, ์์กด์ฑ ๋ฐฐ์ด์ ๋ฐ์์ ์์กด์ฑ์ด ๋ณ๊ฒฝ๋์์ ๋๋ง ์๋ก์ด ๊ฐ์ ์์ฑํ๋ค.
useMemo
๋ ์์กด์ฑ ๋ฐฐ์ด์ ํตํด ๋ฉ๋ชจ์ด์ ์ด์
์ฌ๋ถ๋ฅผ ํ
์ด ์ฑ
์์ง๋ ๊ฒ์ด๋ค.
๋ฐ๋ฉด useMemoCache
๋ ์ปดํ์ผ๋ฌ ๋ด๋ถ์์ ์ฌ์ฉํ๋ ํ
์ผ๋ก, ์์กด์ฑ ๋ฐฐ์ด์ ๋ฐ์ง ์๊ณ , ์บ์๋ฅผ ๊ด๋ฆฌํ๋ ๊ฒ์ด ์ฃผ ๋ชฉ์ ์ด๋ค.
๊ทธ๋ ๊ธฐ ๋ฉ๋ชจ์ด์ ์ด์
์ ์ฑ
์์ useMemoCache
๊ฐ ์๋ ์ปดํ์ผ๋ฌ๊ฐ ๋งก๋๋ค.
useMemoCache
์ ํ
์คํธ ์ฝ๋
์ด์ useMemoCache
๋ฅผ ์ฌ์ฉํ๋ ์์ ์ฝ๋๋ฅผ ์ดํด๋ณด์.
์ข์ ์์๋ค์ด ํ
์คํธ ์ฝ๋์ ๋ง์ด ์๊ธฐ ๋๋ฌธ์, ํ
์คํธ ์ฝ๋๋ฅผ ์ดํด๋ณด๋ฉด ๋์์ ์ดํดํ๋๋ฐ ๋์์ด ๋ ๊ฒ์ด๋ค.
// ๋น์ผ ๊ณ์ฐ์ ํ๋ ํจ์
function someExpensiveProcessing(t) {
Scheduler.log(`Some expensive processing... [${t}]`);
return t;
}
// ์์คํฌ์ค๋ฅผ ํธ๋ฆฌ๊ฑฐ ํ๊ธฐ์ํ ํจ์
function useWithLog(t, msg) {
try {
return React.use(t);
} catch (x) {
Scheduler.log(`Suspend! [${msg}]`);
throw x;
}
}
function Data({chunkA, chunkB}) {
const a = someExpensiveProcessing(useWithLog(chunkA, 'chunkA'));
const b = useWithLog(chunkB, 'chunkB');
return (
<>
{a}
{b}
</>
);
}
function Input() {
const [input, _setText] = useState('');
return input;
}
function App({chunkA, chunkB}) {
return (
<>
<div>
Input: <Input />
</div>
<div>
Data: <Data chunkA={chunkA} chunkB={chunkB} />
</div>
</>
);
}
์ด์ ๊ฐ์ ํํ์ ์ปดํฌ๋ํธ๋ค์ ์ปดํ์ผ ๊ฒฐ๊ณผ๋ฌผ์ ๊ฐ์ง๊ณ ํ
์คํธ๋ฅผ ์งํํด๋ณด์.
Data ์ปดํฌ๋ํธ๋ chunkA์ chunkB๋ฅผ ๋ฐ์์์, chunkA์ ๋ํ ๋น์ผ ๊ณ์ฐ์ ์ํํ๊ณ , chunkB๋ ๊ทธ๋๋ก ๋ฐํํ๋ค.
// react-reconciler/src/__tests__/useMemoCache-test.js
test('์
๋ฐ์ดํธ ์ค์ ์ค๋จ๋(suspended/interrupted) ๋ ๋๋ง ์๋์์์ ๊ณ์ฐ์ ์ฌ์ฌ์ฉํฉ๋๋ค', async () => {
// This test demonstrates the benefit of a shared memo cache. By "shared" I
// mean multiple concurrent render attempts of the same component/hook use
// the same cache. (When the feature flag is off, we don't do this โ the
// cache is copy-on-write.)
// ์ด ํ
์คํธ๋ ๊ณต์ ๋ฉ๋ชจ ์บ์์ ์ด์ ์ ๋ณด์ฌ์ค๋๋ค. "๊ณต์ "๋ผ๋ ๋ง์ ๋์ผํ ์ปดํฌ๋ํธ/ํ
์
// ์ฌ๋ฌ ๋์ ๋ ๋๋ง ์๋๊ฐ ๋์ผํ ์บ์๋ฅผ ์ฌ์ฉํ๋ค๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค.
// (๊ธฐ๋ฅ ํ๋๊ทธ๊ฐ ๊บผ์ ธ ์์ผ๋ฉด ์ด๋ ๊ฒ ํ์ง ์์ต๋๋ค - ์บ์๋ ๋ณต์ฌ๋ณธ์
๋๋ค.)
function Data(t0) {
const $ = useMemoCache(5);
const {chunkA, chunkB} = t0;
const t1 = useWithLog(chunkA, 'chunkA');
let t2;
if ($[0] !== t1) {
t2 = someExpensiveProcessing(t1);
$[0] = t1;
$[1] = t2;
} else {
t2 = $[1];
}
const a = t2;
const b = useWithLog(chunkB, 'chunkB');
let t3;
if ($[2] !== a || $[3] !== b) {
t3 = (
<>
{a}
{b}
</>
);
$[2] = a;
$[3] = b;
$[4] = t3;
} else {
t3 = $[4];
}
return t3;
}
let setInput;
function Input() {
const [input, _set] = useState('');
setInput = _set;
return input;
}
function App(t0) {
const $ = useMemoCache(4);
const {chunkA, chunkB} = t0;
let t1;
if ($[0] === Symbol.for('react.memo_cache_sentinel')) {
t1 = (
<div>
Input: <Input />
</div>
);
$[0] = t1;
} else {
t1 = $[0];
}
let t2;
if ($[1] !== chunkA || $[2] !== chunkB) {
t2 = (
<>
{t1}
<div>
Data: <Data chunkA={chunkA} chunkB={chunkB} />
</div>
</>
);
$[1] = chunkA;
$[2] = chunkB;
$[3] = t2;
} else {
t2 = $[3];
}
return t2;
}
// Resolved ํ๋ก๋ฏธ์ค ์์ฑ
function createInstrumentedResolvedPromise(value) {
return {
then() {},
status: 'fulfilled',
value,
};
}
// Pending ํ๋ก๋ฏธ์ค ์์ฑ
function createDeferred() {
let resolve;
const p = new Promise(res => {
resolve = res;
});
p.resolve = resolve;
return p;
}
์ปดํ์ผ๋ ์ฝ๋๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ด์ ํ ์คํธ๋ฅผ ์งํํด๋ณด์.
์ฒซ ๋ฒ์งธ ๋ ๋๋ง
์ต์ด ๋ ๋๋ง์์๋ chunkA
์ chunkB
๋ฅผ ๋ฐ์์์ Data
์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋งํ๋ค.
createInstrumentedResolvedPromise
๋ Resolved๋ ํ๋ก๋ฏธ์ค๋ฅผ ์์ฑํ๋ค. ์ฆ ์ด๋ฏธ ๋ฐ์์จ ๋ฐ์ดํฐ๋ฅผ ์๋ฏธํ๋ค.
์ด๋๋ A1์ ๊ฑธ๋ ค์๋ expensive process์ ๋ํ ๋ก๊ทธ๊ฐ ์ฐํ๊ณ , Data
์ปดํฌ๋ํธ๋ A1B1
์ ๋ฐํํ๋ค.
// Initial render. We pass the data in as two separate "chunks" to simulate a stream (e.g. RSC).
// ์ต์ด ๋ ๋๋ง. ๋ฐ์ดํฐ๋ฅผ ๋ ๊ฐ์ ๋ณ๋ "์ฒญํฌ"๋ก ์ ๋ฌํ์ฌ ์คํธ๋ฆผ(์: RSC)์ ์๋ฎฌ๋ ์ด์
ํฉ๋๋ค.
const root = ReactNoop.createRoot();
const initialChunkA = createInstrumentedResolvedPromise('A1'); // A1๋ก resolve๋ ํ๋ก๋ฏธ์ค ์์ฑ
const initialChunkB = createInstrumentedResolvedPromise('B1'); // B1๋ก resolve๋ ํ๋ก๋ฏธ์ค ์์ฑ
await act(() =>
root.render(<App chunkA={initialChunkA} chunkB={initialChunkB} />), // ์ด๊ธฐ ๋ ๋๋ง
);
assertLog(['Some expensive processing... [A1]']); //
expect(root).toMatchRenderedOutput(
<>
<div>Input: </div>
<div>Data: A1B1</div>
</>,
);
์ ํ์ค์ UI ์ ๋ฐ์ดํธ
const updatedChunkA = createDeferred();
const updatedChunkB = createDeferred();
createDeferred
ํจ์๋ฅผ ์ฌ์ฉํ์ฌ updatedChunkA
์ updatedChunkB
๋ผ๋ ๋ ๊ฐ์ Promise ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค. ์ด ๊ฐ์ฒด๋ค์ ๋์ค์ ๋ฐ์ดํฐ๋ฅผ ๋ก๋ํ๋ ๋ฐ ์ฌ์ฉ๋๋ค
await act(() => {
React.startTransition(() => {
root.render(<App chunkA={updatedChunkA} chunkB={updatedChunkB} />);
});
});
React.startTransition
์ ์ฌ์ฉํ์ฌ UI ์
๋ฐ์ดํธ๋ฅผ ์์ํ๋ค. transition์ ๋ฎ์ ์ฐ์ ์์๋ก ์ค์ผ์ค๋ง๋์ด, ๋ค๋ฅธ ์์
์ด ๋๋ ํ์ ์คํ๋๋ค.
์ด๋ App
์ปดํฌ๋ํธ์ updatedChunkA
์ updatedChunkB
๋ฅผ ์ ๋ฌํ์ฌ ๋ ๋๋งํ๋ค.
assertLog(['Suspend! [chunkA]']);
const t1 = useWithLog(chunkA, 'chunkA');
useWithLog
ํจ์์์ chunkA
์ ๋ํ useWithLog
๊ฐ ์คํ๋๊ณ , pending ์ค์ ์์์ผ๋ก use
์ ์ํด์ Suspense๊ฐ ํธ๋ฆฌ๊ฑฐ ๋๊ณ โSuspend! [chunkA]โ๊ฐ ์ฐํ๊ฒ ๋๋ค.
await act(() => updatedChunkA.resolve('A2'));
updatedChunkA
๋ฅผ โA2โ๋ก resolveํ๋ค.
const t1 = useWithLog(chunkA, 'chunkA');
let t2;
if ($[0] !== t1) {
t2 = someExpensiveProcessing(t1);
$[0] = t1;
$[1] = t2;
} else {
t2 = $[1];
}
const a = t2;
const b = useWithLog(chunkB, 'chunkB');
๋ฐ์ดํฐ๊ฐ ์ค๋น๋์์ผ๋ฏ๋ก, ๋ ๋๋ง์ด ์ฌ๊ฐ ๋๊ณ , useWithLog
๊ฐ ์คํ๋๋ค.
์ด๋ t1
์ โA2โ๊ฐ ๋๊ณ , $[0]
์๋ ์ด์ ์ ์ ์ฅ๋ โA1โ๊ณผ ๋น๊ตํ์ฌ ๋ค๋ฅด๊ธฐ ๋๋ฌธ์ expensive process๊ฐ ์คํ๋๋ค.
๊ทธ์ ๋ฐ๋ผ โSome expensive processingโฆ [A2]โ๊ฐ ์ฐํ๊ฒ ๋๋ค.
๋ฐ๋ก ๋ค์์ผ๋ก useWithLog
๊ฐ ์คํ๋๊ณ , chunkB๋ ์์ง resolve๋์ง ์์๊ธฐ์ โSuspend! [chunkB]โ๊ฐ ์ฐํ๊ฒ ๋๋ค.
๋ค์ ๋ ๋๋ง์ด ์ค๋จ๋์๊ธฐ ๋๋ฌธ์, ์ด๊ธฐ UI๋ฅผ ๊ทธ๋๋ก ๋ณด์ฌ์ค๋ค.
expect(root).toMatchRenderedOutput(
<>
<div>Input: </div>
<div>Data: A1B1</div>
</>,
);
์ ๋ฐ์ดํธ ์ ํ ์ค ๋ค๋ฅธ ๋ถ๋ถ ์ ๋ฐ์ดํธ
// While waiting for the data to finish loading, update a different part of the screen. This interrupts the refresh transition.
// ๋ฐ์ดํฐ ๋ก๋๊ฐ ์๋ฃ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ๋ ๋์ ํ๋ฉด์ ๋ค๋ฅธ ๋ถ๋ถ์ ์
๋ฐ์ดํธํฉ๋๋ค. ์ด๋ ์๋ก ๊ณ ์นจ ์ ํ์ ์ค๋จํฉ๋๋ค.
//
// In a real app, this might be an input or hover event.
// ์ค์ ์ฑ์์๋ ์
๋ ฅ ๋๋ ํธ๋ฒ ์ด๋ฒคํธ์ผ ์ ์์ต๋๋ค.
await act(() => setInput('hi!')); // ์
๋ ฅ๊ฐ์ 'hi!'๋ก ๋ณ๊ฒฝ
setInput
์ ์ฌ์ฉํ์ฌ ์
๋ ฅ๊ฐ์ โhi!โ๋ก ๋ณ๊ฒฝํ๋ค. ์ด ์
๋ฐ์ดํธ๋ transition์ ์ค๋จ์ํค๊ณ , ์๋ก์ด ์
๋ฐ์ดํธ๋ฅผ ์์ํ๋ค.
// Once the input has updated, we go back to rendering the transition.
// ์
๋ ฅ์ด ์
๋ฐ์ดํธ๋๋ฉด ์ ํ ๋ ๋๋ง์ผ๋ก ๋์๊ฐ๋๋ค.
if (gate(flags => flags.enableNoCloningMemoCache)) {
// We did not have process the first chunk again. We reused the computation from the earlier attempt.
// ์ฒซ ๋ฒ์งธ ์ฒญํฌ๋ฅผ ๋ค์ ์ฒ๋ฆฌํ ํ์๊ฐ ์์์ต๋๋ค. ์ด์ ์๋์์ ๊ณ์ฐ์ ์ฌ์ฌ์ฉํ์ต๋๋ค.
assertLog(['Suspend! [chunkB]']);
} else {
// Because we clone/reset the memo cache after every aborted attempt, we must process the first chunk again.
// ์ค๋จ๋ ์๋๋ง๋ค ๋ฉ๋ชจ ์บ์๋ฅผ ๋ณต์ /์ฌ์ค์ ํ๊ธฐ ๋๋ฌธ์ ์ฒซ ๋ฒ์งธ ์ฒญํฌ๋ฅผ ๋ค์ ์ฒ๋ฆฌํด์ผํฉ๋๋ค.
assertLog(['Some expensive processing... [A2]', 'Suspend! [chunkB]']);
}
enableNoCloningMemoCache
๊ฐ ํ์ฑํ๋์ด ์๋ค๋ฉด, ์ด์ ์ ๊ณ์ฐ๋ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฌ์ฉํ๊ณ , chunkB
์ ๋ํ useWithLog
๊ฐ ์คํ๋์ด โSuspend! [chunkB]โ๊ฐ ์ฐํ๋ค.
ํ์ง๋ง ๋นํ์ฑํ๋์ด ์๋ค๋ฉด, ์์ ๋ณต์ฌ๋ฅผ ํตํด ์บ์๋ฅผ ๊ด๋ฆฌํ๊ธฐ ๋๋ฌธ์, 2์ฐจ์ ๋ฐฐ์ด๋ก ๊ด๋ฆฌ๋๋ memoCache์ ๊ฒฝ์ฐ, $[0] !== t1
์ด true๊ฐ ๋์ด expensive process๊ฐ ์คํ๋๋ค.
expect(root).toMatchRenderedOutput(
<>
<div>Input: hi!</div>
<div>Data: A1B1</div>
</>,
);
์ฌ์ ํ chunkB๋ resolve๋์ง ์์๊ธฐ ๋๋ฌธ์, ์ด์ ๋ ๋๋ง ๊ฒฐ๊ณผ๋ฅผ ๋ณด์ฌ์ค๋ค.
await act(() => updatedChunkB.resolve('B2')); // ์ฒญํฌB๋ฅผ B2๋ก resolve
updatedChunkB
๋ฅผ โB2โ๋ก resolveํ๋ค. ๋ค์ ๋ ๋๋ง์ด ์ฌ๊ฐ๋๋ค.
if (gate(flags => flags.enableNoCloningMemoCache)) {
// We did not have process the first chunk again. We reused the computation from the earlier attempt.
// ์ฒซ ๋ฒ์งธ ์ฒญํฌ๋ฅผ ๋ค์ ์ฒ๋ฆฌํ ํ์๊ฐ ์์์ต๋๋ค. ์ด์ ์๋์์ ๊ณ์ฐ์ ์ฌ์ฌ์ฉํ์ต๋๋ค.
assertLog([]);
} else {
// Because we clone/reset the memo cache after every aborted attempt, we must process the first chunk again.
// ์ค๋จ๋ ์๋๋ง๋ค ๋ฉ๋ชจ ์บ์๋ฅผ ๋ณต์ /์ฌ์ค์ ํ๊ธฐ ๋๋ฌธ์ ์ฒซ ๋ฒ์งธ ์ฒญํฌ๋ฅผ ๋ค์ ์ฒ๋ฆฌํด์ผํฉ๋๋ค.
//
// That's three total times we've processed the first chunk, compared to just once when enableNoCloningMemoCache is on.
// enableNoCloningMemoCache๊ฐ ์ผ์ ธ ์์ ๋ ํ ๋ฒ์ ๋นํด ์ธ ๋ฒ์งธ ์ฒญํฌ๋ฅผ ์ฒ๋ฆฌํ ์ด ํ์์
๋๋ค.
assertLog(['Some expensive processing... [A2]']);
}
expect(root).toMatchRenderedOutput( // ๋ ๋๋ง ๊ฒฐ๊ณผ ํ์ธ
<>
<div>Input: hi!</div>
<div>Data: A2B2</div>
</>,
);
ํ๋๊ทธ๊ฐ ํ์ฑํ๋์ด ์๋ค๋ฉด, ์ด์ ์ ๊ณ์ฐ๋ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฌ์ฉํ๊ธฐ์ ๋ก๊ทธ ์์ด ๋ ๋๋ง์ด ์๋ฃ๋๋ค.
ํ์ง๋ง ๋นํ์ฑํ๋์ด ์๋ค๋ฉด, ์ด์ ์ ๊ณ์ฐ๋ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฌ์ฉํ์ง ์๊ธฐ์ โSome expensive processingโฆ [A2]โ๊ฐ ์ฐํ๊ฒ ๋๋ค.
๊ทธ๋ ๊ฒ Data
์ปดํฌ๋ํธ๋ A2B2
๋ฅผ ๋ฐํํ๊ฒ ๋๋ค.
๋นํ์ฑํ๋์ด์์ผ๋ฉด ์ด 3๋ฒ์ ๋น์ผ ๊ณ์ฐ์ด ์ผ์ด๋๊ฒ ๋๋ค.
ํ์ง๋ง ํ์ฑํ๋์ด์์ผ๋ฉด 1๋ฒ์ ๊ณ์ฐ์ผ๋ก ๋๋๋ค. ๊ฝค๋ ํฐ ์ฐจ์ด์ด๋ค.
ํ์ฌ๋ false๋ก ๋์ด์์ง๋ง, ์ถํ true๋ก ๋ณ๊ฒฝ๋๋ค๋ฉด ํ์ฐจ๋ก ๋ ์ฑ๋ฅ์ด ์ข์์ง ๊ฒ์ด๋ค.
์ ์ด๋ ๊ฒ ์ด์ ํ
์คํธ ์ฝ๋๋ฅผ ํตํด์ ๊น์ง useMemoCache
์ ๋์์ ํ์ธํด๋ณด์๋ค.
๋ง์น๋ฉฐ
์ด๋ฒ ๊ธ์์๋ useMemoCache
์ ๋ํด์ ์์๋ณด์๋ค.
useMemoCache
๋ ์ปดํ์ผ๋ฌ ๋ด๋ถ์์ ์ฌ์ฉ๋๋ ํ
์ผ๋ก, ๋ฉ๋ชจ์ด์ ์ด์
์ ์ํ ์บ์๋ฅผ ๊ด๋ฆฌํ๋ ๊ฒ์ด ์ฃผ ๋ชฉ์ ์ด๋ค.
์์ง ์คํ์ ์ธ ๊ธฐ๋ฅ์ด์ง๋ง ์บ์๋ฅผ ๊ณต์ ํ๋ ๊ธฐ๋ฅ๋ ์ผ๋ํด ๋๊ณ ์๋ ๊ฒ์ผ๋ก ๋ณด์ธ๋ค.
์ด์ ๋ณด๋ค ๋ ์ปดํ์ผ๋ฌ์ ๋ํ ์์ผ๊ฐ ๋์ด์ก๋ค.
์ด์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ด๋ค ๋ฐฉ์์ ํตํด ๋ฉ๋ชจ์ด์ ์ด์
์ ๊ตฌํํ๋์ง ์๊ฒ ๋์์ผ๋, ๋ค์์๋ ์ค์ง์ ์ผ๋ก ์ปดํ์ผ๋ฌ๊ฐ ์ด๋ป๊ฒ ๋์ํ๋์ง์ ๋ํด ์์๋ณด๋ฉด ์ข์ ๊ฒ ๊ฐ๋ค.
ํญ์ ๊ธ์ ์ด๋ป๊ฒ ๋ง๋ฌด๋ฆฌ ์ง์ด์ผ ํ ์ง๊ฐ ๊ณ ๋ฏผ์ด๋ค.
์ด๋ฒ์๋ ์ด๋ ๊ฒ ๊ทธ๋ฅ ๋ง๋ฌด๋ฆฌ ํด๋ณด๊ฒ ๋ค.
์๋ !