본문 바로가기
FRONT-END/React

나만의 React 만들기(Build your own react) - STEP 5

by 랄라J 2024. 9. 6.

https://pomb.us/build-your-own-react/ 을 기반으로 학습 내용을 정리한 글입니다.


STEP 5 - Render and commit Phases

이전 step에서 수정해야 할 코드가 있다. 

if (fiber.parent) {
  fiber.parent.dom.appendChild(fiber.dom);
}

바로 이 부분인데, element에 대해 작업할 때마다 DOM에 새 노드를 추가하고 있다. 전체 트리 렌더링을 완료하기 전에 브라우저가 작업을 중단할 수 있는데, 이 경우 사용자에게는 완전하지 않은 UI가 표시된다.

 

따라서 DOM을 변경하는 부분을 제거하고, 대신 fiber tree의 root를 추적할 것이다.
이를 work in progress root, 줄여서 wipRoot라고 부른다.

function render(element, container) {
  wipRoot = {
    dom: container,
    props: {
      children: [element],
    },
  };
  nextUntilOfWork = wipRoot;
}

let nextUntilOfWork = null;
let wipRoot = null;

그리고 모든 트리 렌더링이 완료되면 전체 fiber tree를 DOM에 그때 커밋한다.
commitRoot 함수를 만들어 여기서 실행시킬 것이다.
이 함수는 모든 노드를 dom에 재귀적으로 추가하는 역할을 한다.

function commitRoot() {
  commitWork(wipRoot.child);
  wipRoot = null;
}

function commitWork(fiber) {
  if (!fiber) {
    return;
  }

  const domParent = fiber.parent.dom;
  domParent.appendChild(fiber.dom);
  commitWork(fiber.child);
  commitWork(fiber.sibling);
}

...

function workLoop(deadline) {
  let shouldYield = false
  while (nextUnitOfWork && !shouldYield) {
    nextUnitOfWork = performUnitOfWork(
      nextUnitOfWork
    )
    shouldYield = deadline.timeRemaining() < 1
  }
​
  if (!nextUnitOfWork && wipRoot) {
    commitRoot()
  }
​
  requestIdleCallback(workLoop)
}
​

 

즉, STEP 5을 완료한 상태의 전체 코드는 아래와 같다.

function createElement(type, props, ...children) {
  return {
    type,
    props: {
      ...props,
      children: children.map((child) =>
        typeof child === "object" ? child : createTextElement(child)
      ),
    },
  };
}

function createTextElement(text) {
  return {
    type: "TEXT_ELEMENT",
    props: {
      nodeValue: text,
      children: [],
    },
  };
}

function createDom(fiber) {
  const dom =
    element.type === "TEXT_ELEMENT"
      ? document.createTextNode("")
      : document.createElement(element.type);

  const isProperty = (key) => key !== "children";
  Object.keys(element.props)
    .filter(isProperty)
    .forEach((name) => {
      dom[name] = element.props[name];
    });

  return dom;
}

function commitRoot() {
  commitWork(wipRoot.child);
  wipRoot = null;
}

function commitWork(fiber) {
  if (!fiber) {
    return;
  }

  const domParent = fiber.parent.dom;
  domParent.appendChild(fiber.dom);
  commitWork(fiber.child);
  commitWork(fiber.sibling);
}

function render(element, container) {
  wipRoot = {
    dom: container,
    props: {
      children: [element],
    },
  };
  nextUntilOfWork = wipRoot;
}

let nextUntilOfWork = null;
let wipRoot = null;

function workLoop(deadline) {
  let shouldYield = false;
  while (nextUntilOfWork && !shouldYield) {
    nextUntilOfWork = performUnitOfWork(nextUntilOfWork);
    shouldYield = deadline.timeRemaining() < 1;
  }

  if (!nextUntilOfWork && wipRoot) {
    commitRoot();
  }

  requestIdleCallback(workLoop);
}

requestIdleCallback(workLoop);

function performUnitOfWork(fiber) {
  if (!fiber.dom) {
    fiber.dom = createDom(fiber);
  }

  if (fiber.parent) {
    fiber.parent.dom.appendChild(fiber.dom);
  }

  const elements = fiber.props.children;
  let index = 0;
  let prevSibling = null;

  while (index < elements.length) {
    const element = elements[index];

    const newFiber = {
      type: element.type,
      props: element.props,
      parent: fiber,
      dom: null,
    };

    if (index === 0) {
      fiber.child = newFiber;
    } else {
      prevSibling.sibling = newFiber;
    }

    prevSibling = newFiber;
    index++;
  }

  if (fiber.child) {
    return fiber.child;
  }

  let nextFiber = fiber;
  while (nextFiber) {
    if (nextFiber.sibling) {
      return nextFiber.sibling;
    }
    nextFiber = nextFiber.parent;
  }
}

const Didact = {
  createElement,
  render,
};

/** @jsx Didact.createElement */
const element = (
  <div id="foo">
    <a>bar</a>
    <b />
  </div>
);

const container = document.getElementById("root");
Didact.render(element, container);

 


여기까지 STEP 5 완료!

 
 
728x90

댓글