VNode 객체로 바꾼 뒤 재귀적으로 다룹니다.key와 위치 기반 경로가 필수입니다. 키가 있는 자식은 문자열 키로, 키가 없는 자식은 인덱스·타입 정보를 이용해 식별합니다.예제
// JSX가 어떤 VNode 트리로 바뀌는지 따라가 보기
const vnode = createElement(
"ul",
null,
createElement("li", { key: "a" }, "Todo A"),
createElement("li", { key: "b" }, "Todo B"),
);
// key가 있는 자식은 경로가 0.ka처럼 key 기반 토큰으로 만들어진다.
예제
// 상태 업데이트가 여러 번 일어나도 render는 한 번만 호출되도록 예약
const render = () => console.log("render once");
const scheduleRender = withEnqueue(render);
scheduleRender();
scheduleRender();
// 콘솔에는 'render once'가 한 번만 출력된다.
type, key를 비교하여 계속 사용할지 판단하는 로직이 핵심입니다. 일치하면 업데이트, 다르면 교체합니다.예제
// 이전 자식 배열
const prev = ["A", "B", "C"].map((text) => createElement("li", { key: text }, text));
// 새 자식 배열 - B와 A 위치가 바뀌고 D가 추가됨
const next = ["B", "A", "C", "D"].map((text) => createElement("li", { key: text }, text));
// 키를 기준으로 하면 기존 DOM을 재활용하면서 최소 이동만 발생한다.
className, style, 일반 속성, 이벤트 핸들러 등 각각 처리 방식이 다릅니다. 예를 들어 이벤트는 addEventListener로 연결하고, 일반 속성은 프로퍼티 혹은 attribute를 선택해야 합니다.onClick → click)하는 과정이 포함됩니다.nodeValue만 바꿉니다.예제
const oldHandler = () => console.log("old click");
const newHandler = () => console.log("new click");
const button = document.createElement("button");
updateDomProps(button, { onClick: oldHandler, style: { color: "red" } }, {
onClick: newHandler,
style: { color: "blue", fontWeight: "bold" },
});
// -> click 리스너는 교체되고, color는 파란색으로 변경, fontWeight 추가, red는 제거됨
Map<경로, Hook[]> 형태로 상태를 보관하고, 렌더 중에는 커서를 증가시키며 현재 훅을 찾습니다.useRef, useMemo, useCallback 등은 내부적으로 useState/useEffect를 활용해 참조 값을 유지하거나 메모이제이션을 수행합니다.예제
function Counter() {
const [count, setCount] = useState(0); // 커서 0
const ref = useRef(null); // 커서 1
useEffect(() => {
ref.current = count;
}, [count]); // 커서 2
return createElement("span", null, count);
}
// 훅 순서가 바뀌면 저장된 커서와 상태 배열이 맞지 않아 오류가 난다.
queueMicrotask 또는 Promise.resolve().then을 사용하면 브라우저 렌더 전에 작업을 한 번 더 예약할 수 있습니다. MiniReact는 이를 이용해 렌더와 이펙트를 배치 처리합니다.useEffect는 별도의 이펙트 큐에 쌓입니다. 각각의 큐는 한 번에 하나씩 실행되도록 플래그를 사용합니다.예제
const queue: Array<{ run: () => void }> = [
{ run: () => console.log("effect 1") },
{ run: () => console.log("effect 2") },
];
const flushEffects = withEnqueue(() => {
while (queue.length) {
queue.shift()?.run();
}
});
flushEffects();
flushEffects();
// effect 1, effect 2가 각각 한 번만 실행된다.
null, undefined, boolean)을 early stage에서 제거하면 이후 단계가 단순해집니다.createRoot(container).render(node)처럼 간결한 진입점을 제공해, 내부 구현을 감추고 학습 비용을 낮춥니다.예제
// shallowEquals를 직접 실험해 보기
shallowEquals({ a: 1, b: 2 }, { a: 1, b: 2 }); // true
shallowEquals({ a: 1 }, { a: 1, b: 3 }); // false
// memo(Component, shallowEquals)을 구성할 때 어떤 props에서 리렌더가 막히는지 알 수 있다.
MiniReact를 스스로 구현하려면 위 지식이 서로 어떻게 맞물리는지 이해해야 합니다. VNode 트리와 경로 규칙으로 컴포넌트를 식별하고, 렌더 사이클과 리컨실리에이션으로 DOM을 최소한으로 갱신하며, Hook 컨텍스트와 스케줄링을 통해 상태·이펙트를 안정적으로 관리합니다. 이 기반 위에서 equality 유틸리티, HOC, 추가 Hook 같은 확장 기능을 자연스럽게 구축할 수 있습니다.