diff --git a/July/article/How-does-useEffect()-work-internally-in-React?.md b/July/article/How-does-useEffect()-work-internally-in-React?.md new file mode 100644 index 0000000..22c579c --- /dev/null +++ b/July/article/How-does-useEffect()-work-internally-in-React?.md @@ -0,0 +1,459 @@ +## ๐Ÿ”— [How does useEffect() work internally in React?](https://jser.dev/2023-07-08-how-does-useeffect-work/) + +### ๐Ÿ—“๏ธ ๋ฒˆ์—ญ ๋‚ ์งœ: 2024.07.17 + +### ๐Ÿงš ๋ฒˆ์—ญํ•œ ํฌ๋ฃจ: ๋ฒ„๊ฑด๋””(์ „ํƒœํ—Œ) + +--- + +> ๋ฆฌ์•กํŠธ 18.2 ๋ฒ„์ „์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ƒˆ๋กœ์šด ๋ฒ„์ „์—์„œ๋Š” ์‹คํ–‰์ด ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +useEffect()๋Š” useState() ๋‹ค์Œ์œผ๋กœ React์—์„œ ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉ๋˜๋Š” ํ›…์ผ ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +๋งค์šฐ ๊ฐ•๋ ฅํ•˜์ง€๋งŒ ๋•Œ๋•Œ๋กœ ํ˜ผ๋ž€์Šค๋Ÿฌ์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‚ด๋ถ€์ ์œผ๋กœ ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€ ์•Œ์•„๋ด…์‹œ๋‹ค. + +```tsx +useEffect(() => { + // ... +}, [deps]); +``` + +## 1. ์ดˆ๊ธฐ ๋งˆ์šดํŠธ ์‹œ์˜ `useEffect()` + +`useEffect()`๋Š” ์ดˆ๊ธฐ ๋งˆ์šดํŠธ ์‹œ์— `mountEffect()`๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + +```tsx +function mountEffect( + create: () => (() => void) | void, + deps: Array | void | null +): void { + return mountEffectImpl( + PassiveEffect | PassiveStaticEffect, // ์ด ํ”Œ๋ž˜๊ทธ๋Š” Layout Effects์™€์˜ ์ฐจ์ด๋ฅผ ๊ตฌ๋ถ„ํ•˜๊ธฐ ์œ„ํ•ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. + HookPassive, + create, + deps + ); +} + +function mountEffectImpl(fiberFlags, hookFlags, create, deps): void { + const hook = mountWorkInProgressHook(); // ์ƒˆ๋กœ์šด hook์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค. + + const nextDeps = deps === undefined ? null : deps; + currentlyRenderingFiber.flags |= fiberFlags; + hook.memoizedState = pushEffect( + // pushEffect()๋Š” Effect ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•œ ํ›„, ํ•ด๋‹น ๊ฐ์ฒด๋ฅผ hook์— ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. + + HookHasEffect | hookFlags, // ์ด ํ”Œ๋ž˜๊ทธ๋Š” ์ดˆ๊ธฐ ๋งˆ์šดํŠธ ์‹œ ์ด ํšจ๊ณผ๋ฅผ ์‹คํ–‰ํ•ด์•ผ ํ•จ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. + create, + undefined, + nextDeps + ); +} +``` + +```tsx +function pushEffect(tag, create, destroy, deps) { + const effect: Effect = { + tag, // tag๋Š” ์ด ํšจ๊ณผ๊ฐ€ ์‹คํ–‰๋  ํ•„์š”๊ฐ€ ์žˆ๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ ํ‘œ์‹œํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋ฏ€๋กœ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. + + create, // ์šฐ๋ฆฌ๊ฐ€ ์ „๋‹ฌํ•˜๋Š” ์ฝœ๋ฐฑ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. + + destroy, // ์ฝœ๋ฐฑ ํ•จ์ˆ˜์—์„œ ๋ฐ˜ํ™˜๋˜๋Š” ์ •๋ฆฌ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. + + deps, // ์šฐ๋ฆฌ๊ฐ€ ์ „๋‹ฌํ•˜๋Š” deps ๋ฐฐ์—ด์ž…๋‹ˆ๋‹ค. + + // Circular + next: (null: any), // ํ•˜๋‚˜์˜ ์ปดํฌ๋„ŒํŠธ์— ์—ฌ๋Ÿฌ ํšจ๊ณผ๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ์ด๋ฅผ ์—ฐ๊ฒฐํ•ฉ๋‹ˆ๋‹ค. + }; + + let componentUpdateQueue: null | FunctionComponentUpdateQueue = (currentlyRenderingFiber.updateQueue: any); + if (componentUpdateQueue === null) { + componentUpdateQueue = createFunctionComponentUpdateQueue(); + currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any); + // ํšจ๊ณผ๋Š” fiber์˜ updateQueue์— ์ €์žฅ๋ฉ๋‹ˆ๋‹ค. + + // ์ด๋Š” hook์˜ memoizedState์™€ ๋‹ค๋ฅด๋‹ค๋Š” ์ ์— ์œ ์˜ํ•˜์„ธ์š”. + + componentUpdateQueue.lastEffect = effect.next = effect; + } else { + const lastEffect = componentUpdateQueue.lastEffect; + if (lastEffect === null) { + componentUpdateQueue.lastEffect = effect.next = effect; + } else { + const firstEffect = lastEffect.next; + lastEffect.next = effect; + effect.next = firstEffect; + componentUpdateQueue.lastEffect = effect; + } + } + return effect; +} +``` + +์šฐ๋ฆฌ๋Š” ์ดˆ๊ธฐ ๋งˆ์šดํŠธ ์‹œ useEffect()๊ฐ€ ํ•„์š”ํ•œ ํ”Œ๋ž˜๊ทธ์™€ ํ•จ๊ป˜ Effect ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋“ค์€ ๋‹ค๋ฅธ ์‹œ์ ์— ์ฒ˜๋ฆฌ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +## 2. ๋ฆฌ๋ Œ๋”๋ง์‹œ์— `useEffect` + +```ts +function updateEffect( + create: () => (() => void) | void, + deps: Array | void | null +): void { + return updateEffectImpl(PassiveEffect, HookPassive, create, deps); +} +function updateEffectImpl(fiberFlags, hookFlags, create, deps): void { + const hook = updateWorkInProgressHook(); + // ํ˜„์žฌ hook ๊ฐ€์ ธ์˜ค๊ธฐ + + const nextDeps = deps === undefined ? null : deps; + let destroy = undefined; + if (currentHook !== null) { + const prevEffect = currentHook.memoizedState; + // Effect hook์˜ memoizedState๋Š” Effect ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค. + + destroy = prevEffect.destroy; + if (nextDeps !== null) { + const prevDeps = prevEffect.deps; + if (areHookInputsEqual(nextDeps, prevDeps)) { + hook.memoizedState = pushEffect(hookFlags, create, destroy, nextDeps); + return; + } + // deps๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์œผ๋ฉด, Effect ๊ฐ์ฒด๋ฅผ ์žฌ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ ์™ธ์—๋Š” ์•„๋ฌด ๊ฒƒ๋„ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + + // ์—ฌ๊ธฐ์„œ Effect ๊ฐ์ฒด๋ฅผ ์žฌ์ƒ์„ฑํ•ด์•ผ ํ•˜๋Š” ์ด์œ ๋Š” updateQueue๋ฅผ ๋‹จ์ˆœํžˆ ์žฌ์ƒ์„ฑํ•  ํ•„์š”๊ฐ€ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + + // ๊ทธ๋ฆฌ๊ณ  ์—…๋ฐ์ดํŠธ๋œ create()๋ฅผ ๊ฐ€์ ธ์™€์•ผ ํ•ฉ๋‹ˆ๋‹ค. + + // ์—ฌ๊ธฐ์„œ๋Š” ์ด์ „ destroy()๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค๋Š” ์ ์— ์ฃผ๋ชฉํ•˜์„ธ์š”. + } + } + currentlyRenderingFiber.flags |= fiberFlags; + hook.memoizedState = pushEffect( + HookHasEffect | hookFlags, + // deps๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด, HookHasEffect๋Š” ์ด ํšจ๊ณผ๋ฅผ ์‹คํ–‰ํ•ด์•ผ ํ•จ์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. + + create, + destroy, + nextDeps + ); +} +``` + +์šฐ๋ฆฌ๋Š” deps ๋ฐฐ์—ด์ด ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ๋ Œ๋”๋งํ•  ๋•Œ, deps๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ๋ฅผ ์ œ์™ธํ•˜๊ณ ๋Š” Effect ๊ฐ์ฒด๋ฅผ ๋‹ค์‹œ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. deps๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด, ์ƒ์„ฑ๋œ Effect๋Š” ์‹คํ–‰๋˜๋„๋ก ํ‘œ์‹œ๋˜๋ฉฐ, ์ด์ „์˜ ์ •๋ฆฌ ํ•จ์ˆ˜๊ฐ€ ํ•จ๊ป˜ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. + +## 3. ํšจ๊ณผ๊ฐ€ ์–ธ์ œ ๊ทธ๋ฆฌ๊ณ  ์–ด๋–ป๊ฒŒ ์‹คํ–‰๋˜๊ณ  ์ •๋ฆฌ๋˜๋Š”๊ฐ€? + +์œ„์—์„œ ์šฐ๋ฆฌ๋Š” `useEffect()`๊ฐ€ ๋‹จ์ˆœํžˆ fiber ๋…ธ๋“œ์— ์ถ”๊ฐ€์ ์ธ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๋ฅผ ์ƒ์„ฑํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ์•˜์Šต๋‹ˆ๋‹ค. ์ด์ œ ์ด๋Ÿฌํ•œ Effect ๊ฐ์ฒด๋“ค์ด ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌ๋˜๋Š”์ง€ ์•Œ์•„๋ณด์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +### 3.1 `commitRoot()`์—์„œ ์ดํŽ™ํŠธ ๋‚ด๋ถ€ ์ฝœ๋ฐฑ ํ•จ์ˆ˜์˜ ์‹คํ–‰์ด ํŠธ๋ฆฌ๊ฑฐ๋จ + +๋‘ ๊ฐœ์˜ fiber ํŠธ๋ฆฌ๋ฅผ ๋น„๊ตํ•˜์—ฌ(diffing) ์–ป์€ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ, ์šฐ๋ฆฌ๋Š” ์ปค๋ฐ‹ ๋‹จ๊ณ„์—์„œ ํ˜ธ์ŠคํŠธ DOM์— ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๋ฐ˜์˜ํ•ฉ๋‹ˆ๋‹ค. ์ดํŽ™ํŠธ ๋‚ด๋ถ€ ์ฝœ๋ฐฑ ํ•จ์ˆ˜์˜ ์‹คํ–‰์„ ์‹œ์ž‘ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์‰ฝ๊ฒŒ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +```ts +function commitRootImpl( + root: FiberRoot, + recoverableErrors: null | Array>, + transitions: Array | null, + renderPriorityLevel: EventPriority, +) { + // ์ˆ˜๋™ ํšจ๊ณผ๊ฐ€ ๋Œ€๊ธฐ ์ค‘์ธ ๊ฒฝ์šฐ, ์ด๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ์ฝœ๋ฐฑ์„ ์˜ˆ์•ฝํ•ฉ๋‹ˆ๋‹ค. + // ๊ฐ€๋Šฅํ•œ ๋นจ๋ฆฌ ์ด ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜์—ฌ, ์ปค๋ฐ‹ ๋‹จ๊ณ„์—์„œ ์˜ˆ์•ฝ๋  ์ˆ˜ ์žˆ๋Š” ๋‹ค๋ฅธ ์ž‘์—…๋ณด๋‹ค ๋จผ์ € ๋Œ€๊ธฐ์—ด์— ๋„ฃ์Šต๋‹ˆ๋‹ค. (์ฐธ์กฐ: #16714) + // TODO: ์ˆ˜๋™ ํšจ๊ณผ ์ฝœ๋ฐฑ์„ ์˜ˆ์•ฝํ•˜๋Š” ๋‹ค๋ฅธ ๋ชจ๋“  ์œ„์น˜๋ฅผ ์‚ญ์ œํ•˜์‹ญ์‹œ์˜ค. + // ์ด๊ฒƒ๋“ค์€ ์ค‘๋ณต์ž…๋‹ˆ๋‹ค. + if ( + (finishedWork.subtreeFlags & PassiveMask) !== NoFlags || + (finishedWork.flags & PassiveMask) !== NoFlags + ) { + if (!rootDoesHavePassiveEffects) { + rootDoesHavePassiveEffects = true; + pendingPassiveEffectsRemainingLanes = remainingLanes; + // workInProgressTransitions๋Š” ๋ฎ์–ด์“ฐ์—ฌ์งˆ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, + // ์ด๋ฅผ ์ฒ˜๋ฆฌํ•  ๋•Œ๊นŒ์ง€ pendingPassiveTransitions์— ์ €์žฅํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. + // ์ด๊ฒƒ์„ commitRoot์— ์ธ์ˆ˜๋กœ ์ „๋‹ฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + // ์™œ๋ƒํ•˜๋ฉด workInProgressTransitions๋Š” ์ด์ „ ๋ Œ๋”์™€ ์ปค๋ฐ‹ ์‚ฌ์ด์— ๋ณ€๊ฒฝ๋  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค + // setTimeout์œผ๋กœ ์ปค๋ฐ‹์„ ์ œํ•œํ•˜๋ฉด + pendingPassiveTransitions = transitions; + scheduleCallback(NormalSchedulerPriority, () => { + flushPassiveEffects(); + // ์ด ๋ Œ๋”๋ง์ด ์ˆ˜๋™ ํšจ๊ณผ๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ–ˆ์Šต๋‹ˆ๋‹ค: ์ˆ˜๋™ ํšจ๊ณผ๊ฐ€ ๋ฐœ์ƒํ•œ ํ›„์— ๋ฃจํŠธ ์บ์‹œ ํ’€์„ ํ•ด์ œํ•ฉ๋‹ˆ๋‹ค. + // ํŠธ๋ฆฌ ๋‚ด์˜ ๋…ธ๋“œ(HostRoot, Cache ๊ฒฝ๊ณ„ ๋“ฑ)๊ฐ€ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ๋Š” ์บ์‹œ ํ’€์„ ํ•ด์ œํ•˜์ง€ ์•Š๋„๋ก ํ•˜๊ธฐ ์œ„ํ•จ์ž…๋‹ˆ๋‹ค. + return null; + }); + // ์—ฌ๊ธฐ์„œ useEffect์— ์˜ํ•ด ์ƒ์„ฑ๋œ ์ˆ˜๋™ ํšจ๊ณผ๋ฅผ ํ”Œ๋Ÿฌ์‹œํ•ฉ๋‹ˆ๋‹ค. + + // ์ด๋Š” ์ฆ‰์‹œ๊ฐ€ ์•„๋‹Œ ๋‹ค์Œ ํ‹ฑ์—์„œ ํ”Œ๋Ÿฌ์‹ฑ์„ ์˜ˆ์•ฝํ•ฉ๋‹ˆ๋‹ค. + + // ์ž์„ธํ•œ ๋‚ด์šฉ์€ React ์Šค์ผ€์ค„๋Ÿฌ ์ž‘๋™ ๋ฐฉ์‹์„ ์ฐธ์กฐํ•˜์‹ญ์‹œ์˜ค. + } + } + ... +} + +``` + +## 3.2 flushPassiveEffects() + +```ts +function flushPassiveEffectsImpl() { + if (rootWithPendingPassiveEffects === null) { + return false; + } + // ์ „ํ™˜ ํ”Œ๋ž˜๊ทธ๋ฅผ ์บ์‹œํ•˜๊ณ  ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค. + const transitions = pendingPassiveTransitions; + pendingPassiveTransitions = null; + const root = rootWithPendingPassiveEffects; + const lanes = pendingPassiveEffectsLanes; + rootWithPendingPassiveEffects = null; + // TODO: ์ด๊ฒƒ์€ ๋•Œ๋•Œ๋กœ rootWithPendingPassiveEffects์™€ ๋™๊ธฐํ™”๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + // ์™œ ๊ทธ๋Ÿฐ์ง€ ์ฐพ์•„์„œ ์ˆ˜์ •ํ•˜์‹ญ์‹œ์˜ค. ์•Œ๋ ค์ง„ ๋ฌธ์ œ๋ฅผ ์ผ์œผํ‚ค์ง€๋Š” ์•Š์ง€๋งŒ(์•„๋งˆ๋„ ํ”„๋กœํŒŒ์ผ๋ง์—๋งŒ ์‚ฌ์šฉ๋˜๊ธฐ ๋•Œ๋ฌธ์—), + // ๋ฆฌํŒฉํ„ฐ๋ง ์œ„ํ—˜์ด ์žˆ์Šต๋‹ˆ๋‹ค. + pendingPassiveEffectsLanes = NoLanes; + const prevExecutionContext = executionContext; + executionContext |= CommitContext; + commitPassiveUnmountEffects(root.current); + commitPassiveMountEffects(root, root.current, lanes, transitions); + // ์—ฌ๊ธฐ์„œ ์šฐ๋ฆฌ๋Š” ์ฝœ๋ฐฑ์ด ์‹คํ–‰๋˜๊ธฐ ์ „์— ํšจ๊ณผ ์ •๋ฆฌ๊ฐ€ ๋จผ์ € ์‹คํ–‰๋˜๋Š” ๊ฒƒ์„ ๋ช…ํ™•ํžˆ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + + ... +} +``` + +## 3.3 commitPassiveUnmountEffects() + +```tsx +export function commitPassiveUnmountEffects(finishedWork: Fiber): void { + setCurrentDebugFiberInDEV(finishedWork); + commitPassiveUnmountOnFiber(finishedWork); + resetCurrentDebugFiberInDEV(); +} +function commitPassiveUnmountOnFiber(finishedWork: Fiber): void { + switch (finishedWork.tag) { + case FunctionComponent: + case ForwardRef: + case SimpleMemoComponent: { + recursivelyTraversePassiveUnmountEffects(finishedWork); + // ์—ฌ๊ธฐ์„œ๋Š” ์ž์‹์˜ ํšจ๊ณผ๋“ค์ด ๋จผ์ € ์ •๋ฆฌ๋ฉ๋‹ˆ๋‹ค. + + if (finishedWork.flags & Passive) { + commitHookPassiveUnmountEffects( + finishedWork, + finishedWork.return, + HookPassive | HookHasEffect, + // ์ด ํ”Œ๋ž˜๊ทธ๋Š” ์˜์กด์„ฑ์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์œผ๋ฉด ์ฝœ๋ฐฑ์ด ์‹คํ–‰๋˜์ง€ ์•Š๋„๋ก ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค. + ); + } + break; + } + ... + } +} +function commitHookPassiveUnmountEffects( + finishedWork: Fiber, + nearestMountedAncestor: null | Fiber, + hookFlags: HookFlags, +) { + if (shouldProfile(finishedWork)) { + startPassiveEffectTimer(); + commitHookEffectListUnmount( + hookFlags, + finishedWork, + nearestMountedAncestor, + ); + recordPassiveEffectDuration(finishedWork); + } else { + commitHookEffectListUnmount( + hookFlags, + finishedWork, + nearestMountedAncestor, + ); + } +} +function commitHookEffectListUnmount( + flags: HookFlags, + finishedWork: Fiber, + nearestMountedAncestor: Fiber | null, +) { + const updateQueue: FunctionComponentUpdateQueue | null = + (finishedWork.updateQueue: any); + const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null; + if (lastEffect !== null) { + const firstEffect = lastEffect.next; + let effect = firstEffect; + do { + if ((effect.tag & flags) === flags) { + // ์–ธ๋งˆ์šดํŠธ + const inst = effect.inst; + const destroy = inst.destroy; + if (destroy !== undefined) { + inst.destroy = undefined; + safelyCallDestroy(finishedWork, nearestMountedAncestor, destroy); + } + } + effect = effect.next; + } while (effect !== firstEffect); + // ์—ฌ๊ธฐ์„œ๋Š” updateQueue์˜ ๋ชจ๋“  Effect๋“ค์„ ์ˆœํšŒํ•˜๊ณ  + // ํ”Œ๋ž˜๊ทธ์— ๋งž๋Š” ๊ฒƒ๋“ค๋งŒ ํ•„ํ„ฐ๋งํ•ฉ๋‹ˆ๋‹ค. + } +} +function safelyCallDestroy( + current: Fiber, + nearestMountedAncestor: Fiber | null, + destroy: () => void, +) { + try { + destroy(); + } catch (error) { + captureCommitPhaseError(current, nearestMountedAncestor, error); + } +} +``` + +## 3.4 commitPassiveMountEffects() + +`commitPassiveMountEffects` ๋˜ํ•œ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. + +```tsx +export function commitPassiveMountEffects( + root: FiberRoot, + finishedWork: Fiber, + committedLanes: Lanes, + committedTransitions: Array | null, +): void { + setCurrentDebugFiberInDEV(finishedWork); + commitPassiveMountOnFiber( + root, + finishedWork, + committedLanes, + committedTransitions, + ); + resetCurrentDebugFiberInDEV(); +} +function commitPassiveMountOnFiber( + finishedRoot: FiberRoot, + finishedWork: Fiber, + committedLanes: Lanes, + committedTransitions: Array | null, +): void { + // ์ด ํ•จ์ˆ˜๋ฅผ ์—…๋ฐ์ดํŠธํ•  ๋•Œ๋Š”, offscreen ํŠธ๋ฆฌ๊ฐ€ ์ˆจ๊น€ -> ํ‘œ์‹œ๋กœ ์ „ํ™˜๋  ๋•Œ ๋˜๋Š” ์ˆจ๊ฒจ์ง„ ํŠธ๋ฆฌ ๋‚ด๋ถ€์˜ ํšจ๊ณผ๋ฅผ ํ† ๊ธ€ํ•  ๋•Œ ๋Œ€๋ถ€๋ถ„ ๋™์ผํ•œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” reconnectPassiveEffects๋„ ์—…๋ฐ์ดํŠธํ•˜์‹ญ์‹œ์˜ค. + const flags = finishedWork.flags; + switch (finishedWork.tag) { + case FunctionComponent: + case ForwardRef: + case SimpleMemoComponent: { + recursivelyTraversePassiveMountEffects( + // ์—ฌ๊ธฐ์„œ๋Š” ์ž์‹์˜ ํšจ๊ณผ๋“ค์ด ๋จผ์ € ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. + finishedRoot, + finishedWork, + committedLanes, + committedTransitions, + ); + if (flags & Passive) { + commitHookPassiveMountEffects( + finishedWork, + HookPassive | HookHasEffect, + // ์ด ํ”Œ๋ž˜๊ทธ๋Š” ์˜์กด์„ฑ์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์œผ๋ฉด ์ฝœ๋ฐฑ์ด ์‹คํ–‰๋˜์ง€ ์•Š๋„๋ก ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค. + ); + } + break; + } + ... + } +} +function commitHookPassiveMountEffects( + finishedWork: Fiber, + hookFlags: HookFlags, +) { + if (shouldProfile(finishedWork)) { + startPassiveEffectTimer(); + try { + commitHookEffectListMount(hookFlags, finishedWork); + } catch (error) { + captureCommitPhaseError(finishedWork, finishedWork.return, error); + } + recordPassiveEffectDuration(finishedWork); + } else { + try { + commitHookEffectListMount(hookFlags, finishedWork); + } catch (error) { + captureCommitPhaseError(finishedWork, finishedWork.return, error); + } + } +} +function commitHookEffectListMount(flags: HookFlags, finishedWork: Fiber) { + const updateQueue: FunctionComponentUpdateQueue | null = + (finishedWork.updateQueue: any); + const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null; + if (lastEffect !== null) { + const firstEffect = lastEffect.next; + let effect = firstEffect; + do { + if ((effect.tag & flags) === flags) { + // ๋งˆ์šดํŠธ + const create = effect.create; + const inst = effect.inst; + const destroy = create(); + // ์ฝœ๋ฐฑ์ด ์—ฌ๊ธฐ์„œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค! + inst.destroy = destroy; + } + effect = effect.next; + } while (effect !== firstEffect); + // ํ•„์š”ํ•œ Effect๋“ค์„ ํ•„ํ„ฐ๋งํ•˜๊ณ  ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. + } +} +``` + +## 4. ์š”์•ฝ + +์†Œ์Šค ์ฝ”๋“œ๋ฅผ ์‚ดํŽด๋ณธ ๊ฒฐ๊ณผ, `useEffect()`์˜ ๋‚ด๋ถ€ ๊ตฌ์กฐ๋Š” ๋งค์šฐ ๊ฐ„๋‹จํ•˜๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. + +1. `useEffect()`๋Š” fiber์— ์ €์žฅ๋˜๋Š” Effect ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. + +- Effect๋Š” ์‹คํ–‰์ด ํ•„์š”ํ•œ์ง€๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ํƒœ๊ทธ(tag)๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. +- Effect๋Š” ์šฐ๋ฆฌ๊ฐ€ ์ฒซ ๋ฒˆ์งธ ์ธ์ˆ˜๋กœ ์ „๋‹ฌํ•˜๋Š” create()๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. +- Effect๋Š” create()์—์„œ์˜ ์ •๋ฆฌ ์ž‘์—…์ธ destroy()๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฉฐ, ์ด๋Š” create()๊ฐ€ ์‹คํ–‰๋  ๋•Œ๋งŒ ์„ค์ •๋ฉ๋‹ˆ๋‹ค. + +2. `useEffect()`๋Š” ๋งค๋ฒˆ ์ƒˆ๋กœ์šด Effect ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜์ง€๋งŒ, ์˜์กด์„ฑ ๋ฐฐ์—ด์ด ๋ณ€๊ฒฝ๋  ๊ฒฝ์šฐ ๋‹ค๋ฅธ ํƒœ๊ทธ๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. + +3. ํ˜ธ์ŠคํŠธ DOM์— ์—…๋ฐ์ดํŠธ๋ฅผ ์ปค๋ฐ‹ํ•  ๋•Œ, ๋‹ค์Œ ํ‹ฑ(tick)์—์„œ ๋ชจ๋“  Effect๋ฅผ ํƒœ๊ทธ์— ๋”ฐ๋ผ ๋‹ค์‹œ ์‹คํ–‰ํ•˜๋„๋ก ์ž‘์—…์ด ์˜ˆ์•ฝ๋ฉ๋‹ˆ๋‹ค. + +- ์ž์‹ ์ปดํฌ๋„ŒํŠธ์˜ Effect๊ฐ€ ๋จผ์ € ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค. +- ์ •๋ฆฌ ์ž‘์—…์ด ๋จผ์ € ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. + +## ํ€ด์ฆˆ + +์˜ค๋Š˜ ๋ฐฐ์šด๊ฒƒ์„ ํ† ๋Œ€๋กœ ํ•œ๋ฒˆ ์œ„ ๋ฌธ์ œ๋ฅผ ํ’€์–ด๋ณด์„ธ์š”. + +```tsx +function App() { + const [count, setCount] = useState(1); + + console.log(1); + + useEffect(() => { + console.log(2); + return () => { + console.log(3); + }; + }, [count]); + + useEffect(() => { + console.log(4); + setCount((count) => count + 1); + }, []); + + return ; +} +function Child({ count }) { + useEffect(() => { + console.log(5); + return () => { + console.log(6); + }; + }, [count]); + + return null; +} + +const root = createRoot(document.getElementById("root")); +root.render(); +```