React Fiber ํํค์น๊ธฐ โ DOM ์์์์ ์ปดํฌ๋ํธ ํธ๋ฆฌ๋ก ๊ฑฐ์ฌ๋ฌ ์ฌ๋ผ๊ฐ๊ธฐ
์ฐํด๋ฆญ์ผ๋ก ์ปดํฌ๋ํธ ์์ค๋ก ์ ํํ๋ ๋๊ตฌ๋ฅผ ๋ง๋ค ๋, ๋ฐํ์์ ํต์ฌ ์ง๋ฌธ์ ์ด๊ฑฐ์๋ค.
์ฌ์ฉ์๊ฐ ํด๋ฆญํ ์ด DOM ์์๊ฐ, ๋์ฒด ์ด๋ React ์ปดํฌ๋ํธ์์ ๋์จ ๊ฑธ๊น?
๋ธ๋ผ์ฐ์ ์ ์ค์ ๋ก ์กด์ฌํ๋ ๊ฑด <div>, <button> ๊ฐ์ DOM ๋
ธ๋๋ฟ์ด๋ค.
React ์ปดํฌ๋ํธ๋ ๋ ๋๋ง์ด ๋๋๋ฉด ์ฌ๋ผ์ง ๊ฒ์ฒ๋ผ ๋ณด์ธ๋ค. ํ์ง๋ง React๋
๊ทธ ์ฐ๊ฒฐ๊ณ ๋ฆฌ๋ฅผ DOM์ ๋ชฐ๋ ๋งค๋ฌ์ ๋๋ค. ๊ทธ๊ฒ Fiber๋ค. ์ด ๊ธ์ Fiber๋ฅผ
ํ๊ณ ๋ค์ด "DOM โ ์ปดํฌ๋ํธ"๋ฅผ ๋์ง๋ ๋ฐฉ๋ฒ์ ์ ๋ฆฌํ๋ค.
Fiber๊ฐ ๋ญ๊ฐ (์์ฃผ ์งง๊ฒ)
React 16๋ถํฐ ๋ด๋ถ ๋ ๋๋ง ๋จ์๋ Fiber๋ผ๋ ๊ฐ์ฒด๋ค. ์ปดํฌ๋ํธ ํ๋, JSX ์์ ํ๋๊ฐ ๊ฐ๊ฐ Fiber ๋ ธ๋๊ฐ ๋๊ณ , ์ด๋ค์ด ํธ๋ฆฌ๋ฅผ ์ด๋ฃฌ๋ค. ์ฐ๋ฆฌ๊ฐ ํธ๋ฆฌ๋ฅผ ๊ฑฐ์ฌ๋ฌ ์ฌ๋ผ๊ฐ ๋ ์ธ ํ๋๋ ์ด ์ ๋๋ค.
returnโ ๋ถ๋ชจ Fiber (ํธ๋ฆฌ๋ฅผ ์๋ก ์ฌ๋ผ๊ฐ ๋ ์ด๋ค)type/elementTypeโ ์ด Fiber๊ฐ ๋ฌด์์ธ๊ฐ. ํธ์คํธ ์์๋ฉด"div"๊ฐ์ ๋ฌธ์์ด, ์ปดํฌ๋ํธ๋ฉด ๊ทธ ํจ์/ํด๋์ค ์์ฒดpendingProps/memoizedPropsโ ์ด ์์์ props_debugSourceโ (React 18๊น์ง) ์๋ณธ ์์ค ์์น. React 19์์ ์ ๊ฑฐ๋จ
1. DOM ์์์์ Fiber ๊บผ๋ด๊ธฐ
React๋ DOM ๋
ธ๋์ __reactFiber$โฆ(๋๋ ์ ๋ฒ์ ์
__reactInternalInstance$โฆ)๋ผ๋ ๋๋ค ์ ๋ฏธ์ฌ๊ฐ ๋ถ์ ํค๋ก Fiber๋ฅผ ๋งค๋ฌ์
๋๋ค. ์ ๋ฏธ์ฌ๊ฐ ๋งค๋ฒ ๋ฐ๋๋, ํค ์ด๋ฆ์ ์ง์ ํ๋์ฝ๋ฉํ ์ ์๊ณ ์ฐพ์์ผ ํ๋ค.
const fiberKeyCache = new WeakMap<Element, string | undefined>();
function getReactFiberKey(element: Element) {
if (fiberKeyCache.has(element)) return fiberKeyCache.get(element);
const key = Object.keys(element).find(
(k) => k.startsWith("__reactFiber$") ||
k.startsWith("__reactInternalInstance$"),
);
fiberKeyCache.set(element, key); // ์ ๋ฏธ์ฌ๋ ์ธ์
๋ด ๊ณ ์ โ ์บ์
return key;
}
Object.keys(element)๋ก ํค๋ฅผ ๋ค์ง๋ ๊ฑด ๊ณต์ง๊ฐ ์๋๋ผ์ WeakMap์ ์บ์ํ๋ค.
WeakMap์ด๋ผ ์์๊ฐ ์ฌ๋ผ์ง๋ฉด ์บ์๋ ์๋์ผ๋ก ์ ๋ฆฌ๋๋ค.
ํด๋ฆญํ ์์์ Fiber๊ฐ ์์ ์๋ ์๋ค(ํ ์คํธ ๋ ธ๋๋ ์์ DOM). ๊ทธ๋ด ๋ ๋ถ๋ชจ๋ก ํ ์นธ์ฉ ์ฌ๋ผ๊ฐ๋ฉฐ Fiber๋ฅผ ๊ฐ์ง ๊ฐ์ฅ ๊ฐ๊น์ด ์์๋ฅผ ์ฐพ๋๋ค.
function getClosestReactFiber(target: Element | null) {
let current = target;
while (current) {
const fiberKey = getReactFiberKey(current);
if (fiberKey) {
return (current as any)[fiberKey] as ReactFiber;
}
current = current.parentElement;
}
return null;
}
2. Fiber ํธ๋ฆฌ๋ฅผ ๊ฑฐ์ฌ๋ฌ ์ฌ๋ผ๊ฐ๋ฉฐ ์์ค ์ฐพ๊ธฐ
Fiber ํ๋๋ฅผ ์ก์์ผ๋ฉด, ์ด์ return(๋ถ๋ชจ)์ ๋ฐ๋ผ ์๋ก ์ฌ๋ผ๊ฐ๋ฉด์
๋น๋ ํ์์ ์ฌ์ด๋ ์์ค ์ ๋ณด๋ฅผ ๋ชจ์๋ค. ์ ๋ณด๋ ๋ ๊ตฐ๋ฐ์ ์๋ค.
- ์ปดํฌ๋ํธ ์ชฝ:
fiber.type/elementType(= ์ปดํฌ๋ํธ ํจ์)์ ๋ถ์ฌ๋__componentSourceLoc - JSX ์์ ์ชฝ:
props์์ ๋ฃ์ด๋$componentSourceLoc
function getSourceFromType(type: unknown) {
if (typeof type === "function") {
// ์ปดํฌ๋ํธ ํจ์์ ๋ถ์ฌ๋ ์์ค ์์น
const source = (type as any).__componentSourceLoc;
return typeof source === "string" ? source : null;
}
// memo/forwardRef ๋ก ๊ฐ์ธ์ง๋ฉด type์ด ๊ฐ์ฒด๋ผ, ํ ๊ฒน ๋ ๋ค์ด๊ฐ ๋ณธ๋ค
if (type && typeof type === "object") {
const t = type as any;
return t.__componentSourceLoc
?? t.type?.__componentSourceLoc // memo(...)
?? t.render?.__componentSourceLoc // forwardRef(...)
?? null;
}
return null;
}
์ฌ๊ธฐ์ ์ฌ๋ฏธ์๋ ์ง์ : memo(Comp)๋ forwardRef(Comp)๋ type์ด ํจ์๊ฐ
์๋๋ผ ๊ฐ์ฒด๋ค. memo๋ .type์, forwardRef๋ .render์ ์ค์ ์ปดํฌ๋ํธ๊ฐ
๋ค์ด ์๋ค. ๊ทธ๋์ ํ ๊ฒน์ฉ ๋ ๋ค์ฌ๋ค๋ด์ผ ์์ค๋ฅผ ๋์น์ง ์๋๋ค. ์ด๋ฐ ๋ํ
์ผ์ด์ค๋ฅผ ์ฒ๋ฆฌํ๋ ๊ฒ ์ค์ ์์ ์ ์ผ ์์ด ๋ง์ด ๊ฐ๋ค.
3. "ํ๋ฉด ์์น"์ "๊ตฌํ ์์น"๋ ๋ค๋ฅด๋ค
ํธ๋ฆฌ๋ฅผ ์ฌ๋ผ๊ฐ๋ค ๋ณด๋ฉด ์์ค ํ๋ณด๊ฐ ์ฌ๋ฌ ๊ฐ ๋์จ๋ค. ์๋ฅผ ๋ค์ด ๋์์ธ
์์คํ
์ <Button>์ ํด๋ฆญํ๋ฉด:
- ๋ด๊ฐ
<Button>์ ์ด ์๋ฆฌ (์:CheckoutPage.tsx:42) โ ํ๋ฉด(screen) Button์ด ์ ์๋ ์๋ฆฌ (์:node_modules/ui/Button.tsx) โ ๊ตฌํ(implementation)
๋์์ธ์ ๊ณ ์น๋ ค๋ ์ฌ๋์ ๋ณดํต ๋ด๊ฐ ์ด ์๋ฆฌ๋ฅผ ์ํ๋ค. ๊ทธ๋์ ํ๋ณด๋ค์ ๋ชจ์ ๋ค, "ํ๋ก์ ํธ ๋ก์ปฌ(๋ด ์ฝ๋)์ธ์ง"๋ฅผ ๊ธฐ์ค์ผ๋ก ํ๋ฉด์ฉ/๊ตฌํ์ฉ์ ๋๋ด๋ค.
// ๋ด ํ๋ก์ ํธ ์์ ์์ค์ธ๊ฐ? (node_modulesยท์์ ๊ฒฝ๋ก๋ฉด ์ ์ธ)
const isLocal = (source) => isProjectLocalSource(source, projectRoot);
// screen: ๊ฐ์ฅ ๊ฐ๊น์ด ํ๋ก์ ํธ ๋ก์ปฌ ์์น
// implementation: ํ๋ก์ ํธ ์ธ๋ถ(๊ณต์ ์ปดํฌ๋ํธ) ์์น
Alt+1(screen) / Alt+2(implementation)๋ก ๋ชจ๋๋ฅผ ํ ๊ธํ๊ฒ ๋ง๋ ์ด์ ๊ฐ
์ด๊ฑฐ๋ค. ๊ฐ์ ํฝ์
์ ํด๋ฆญํด๋ "์ ๋ต ์์ค"๊ฐ ํ๋๊ฐ ์๋๊ธฐ ๋๋ฌธ์ด๋ค.
_debugSource์ ๊ธฐ๋์ง ์๊ธฐ๋ก ํ ์ด์
์์ ์ ์ด ๋ชจ๋ ๊ฑธ fiber._debugSource ํ๋๋ก ๋์ถฉ ํด๊ฒฐํ ์ ์์๋ค. React๊ฐ
๊ฐ๋ฐ ๋ชจ๋์์ ์๋ณธ ์์น๋ฅผ ๋ถ์ฌ์คฌ์ผ๋๊น. ๊ทธ๋ฐ๋ฐ React 19์์ ์ด๊ฒ ๋น ์ก๋ค.
๊ทธ๋์ ๋ฐฉํฅ์ ๋ฐ๊ฟจ๋ค. React๊ฐ ์ฃผ๋ ๊ฑธ ์ฐ๋ ๋์ , ๋ด๊ฐ ๋น๋ ํ์์ ์ง์
__componentSourceLoc/$componentSourceLoc๋ฅผ ์ฌ๊ณ , ๋ฐํ์์ Fiber๋ฅผ ๋ค์ ธ
๊ทธ๊ฑธ ์ฝ๋๋ค. ์ฝ๋๋ ์ด legacy ํ๋๋ ํด๋ฐฑ์ผ๋ก ํจ๊ป ์ง์ํ๋ค.
const raw =
getSourceFromProps(fiber.pendingProps) ??
getSourceFromProps(fiber.memoizedProps) ??
getDirectDebugSource(fiber); // _debugSource ํด๋ฐฑ (React โค18)
๊ฒฐ๊ณผ์ ์ผ๋ก React ๋ฒ์ ์ ๋ ๋ฆฝ์ ์ธ ๋ ํผํผํ ๊ตฌ์กฐ๊ฐ ๋๋ค. ํ๋ ์์ํฌ ๋ด๋ถ ๊ตฌํ์ ์์กดํ ๋์ ์ํ(์ด๋ ๋ ์กฐ์ฉํ ์ฌ๋ผ์ง๋ค)์ ์ ๋ฉด์ผ๋ก ๋ฐฐ์ด ์ฌ๋ก์๋ค.
์ ๋ฆฌ
- React๋ DOM ๋
ธ๋์
__reactFiber$โฆํค๋ก Fiber๋ฅผ ๋งค๋ฌ์ ๋๋ค. getClosestReactFiberโfiber.return์ผ๋ก ํธ๋ฆฌ๋ฅผ ์๋ก ์ํํ๋ฉด DOM์์ ์ปดํฌ๋ํธ ๊ณ๋ณด๋ฅผ ๋์ง์ ์ ์๋ค.type/elementType์์ ์ปดํฌ๋ํธ๋ฅผ,props์์ JSX ์์ ์์ค๋ฅผ ์ฝ๋๋ค.memo/forwardRef๋ ํ ๊ฒน ๋ ๋ค์ด๊ฐ์ผ ํ๋ค.- ๊ฐ์ UI๋ผ๋ ํ๋ฉด ์์น โ ๊ตฌํ ์์น. ๋ ๋ค ํ์ํ๋ค.
Fiber๋ "React๊ฐ ๋ ๋๋ง์ ์ด๋ป๊ฒ ๊ธฐ์ตํ๋๊ฐ"์ ์ค์ฒด๋ค. ํ ๋ฒ ์ด์ด๋ณด๋ฉด DevTools๊ฐ ์ด๋ป๊ฒ ๋์ํ๋์ง, ์ ๊ทธ๋ฐ ์ ์ฝ์ด ์๋์ง๊ฐ ์ ๋ถ ๋ณด์ธ๋ค.
- ์ค์ ๊ตฌํ: react-code-locator (GitHub)
- ์ด์ด์ง๋ ๊ธ: ์ฝ๋๋ ์ด๋ป๊ฒ ํธ๋ฆฌ๊ฐ ๋๋๊ฐ โ AST์ ํ์ฑ