본문 바로가기
FRONT-END/React

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

by 랄라J 2024. 9. 5.

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


STEP 4 - Fibers

이제 앞선 step에 이어 작업 단위를 구성해야 한다.
이때 fiber tree라는 데이터 구조가 필요하다.
각 element마다 하나의 fiber가 있고 각 fiber는 작업 단위가 된다.

 

Didact.render(
  <div>
    <h1>
      <p />
      <a />
    </h1>
    <h2 />
  </div>,
  container
);

위 코드를 Fiber Tree로 나타내면 다음과 같다.

출처 : https://pomb.us/build-your-own-react/

 

위 코드를 element tree로 렌더링한다고 가정해 보자.
root fiber를 생성하고, nextUntilOfWork로 설정한다.
나머지 작업은 PerformUnitOfWork 함수에서 수행되며 각 fiber에 대해 3가지 작업을 수행한다.

1. element를 DOM에 추가
2. element의 children에 대한 fiber를 생성
3. 다음 작업 단위 선택

 

fiber tree 구조의 목표 중 하나는 다음 작업 단위를 쉽게 찾을 수 있도록 하는 것이다.
fiber가 첫 번째 자식, 다음 형제 및 부모에 대한 링크를 갖는 이유다.

fiber에 대한 작업 수행이 끝나면, 해당 fiber가 다음 작업 단위가 된다.
위 코드에서 div fiber 작업을 마치면 다음 작업 단위는 h1이 된다.

만약 fiber가 child를 가지지 않는다면 다음 작업으로 sibling이 사용되고, sibling도 가지지 않는다면 uncle로 이동한다.

출처 : https://pomb.us/build-your-own-react/

위 코드에서 a fiber 작업을 마치면 h2로 이동하는 부분이 uncle로 이동에 해당한다.

이 작업들은 root에 도달할 때까지 부모를 통해 계속 올라간다.
root에 도달했다면 렌더링에 대한 모든 작업 수행을 완료했음을 의미한다.

 


여기서 잠깐✋, Fiber란 뭔지 살펴보고 가자.

- Fiber는 React에서 각 UI 요소를 나타내는 데이터 구조다. 이는 Virtual DOM에서 각 요소를 더 세밀하게 관리하고 업데이트하기 위한 기반을 제공한다.
- Fiber는 작업 단위다. React의 업데이트는 Fiber Tree의 작업을 통해 이루어지며, 각 Fiber는 해당 UI 요소에 대한 정보를 담고 있다.

Fiber 아키텍처의 핵심 요소

- Fiber Node : 각 Fiber는 React 컴포넌트나 DOM 요소를 나타내는 데이터 구조다. Fiber 객체는 컴포넌트의 상태, 프로퍼티, 자식, 부모 등의 정보를 포함한다.
- Fiber Tree : Fiber는 Tree 구조로 연결되어 있다. 각 Fiber는 부모 Fiber와 자식 Fiber를 가지며 전체 UI 구조를 나타내는 트리를 형성한다.
- 작업의 우선순위 : Fiber는 우선순위에 따라 작업을 나누어 처리한다. 높은 우선순위를 가진 작업은 먼저 수행되고, 낮은 우선순위 작업은 나중에 수행된다. 작업은 slice 형태로 수행되며, 이로 인해 UI 업데이트를 더 부드럽고 동적으로 처리할 수 있다.
- 비동기 작업 처리 : Fiber는 비동기적으로 작업을 처리할 수 있도록 설계되어 있다. 이는 긴 작업을 여러 프레임에 걸쳐 나누어 처리할 수 있게 해 준다.


다시 돌아와서 코드를 작성해 보자!

DOM 노드를 생성하는 부분을 함수에 보관하고 나중에 사용할 예정이다.
우선 render 함수를 createDom으로 함수명을 변경한다.

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

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

  return dom;
}

 

다시 render 함수를 작성할 건데, render 함수는 nextUnitOfWork를 Fiber tree의 root로 설정하는 역할을 한다.

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

 

그다음 브라우저가 준비되면 workLoop를 호출하고 root에서 작업을 시작한다.

아래의 순서로 performUnitOfWork 함수를 채울 것이다.

 

1. 새 노드 생성 후 이를 DOM 노드에 추가한다. fiber.dom 속성에서 DOM 노드를 추적한다.

  // 1. add dom node
  if (!fiber.dom) {
    fiber.dom = createDom(fiber);
  }

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

위 코드에서 fiber.parent를 체크하고 추가하는 부분을 부연 설명하자면 (왜냐면 다시 이 글을 보는 스스로가 의문이 생겼기 때문입니다.)

fiber는 React의 Fiber 구조에서 하나의 컴포넌트를 나타내는 객체입니다. 여기서 fiber.parent는 해당 부모의 컴포넌트를 의미합니다.

fiber가 부모 컴포넌트를 가진 경우에만 DOM 트리에 추가되도록 작업되는 것이죠. 부모 노드가 있어야 자식 노드를 추가할 수 있으니까, 부모가 없는 경우 이 작업을 하지 않는 것입니다.


2. 각 children마다 새로운 fiber를 만든다. 그리고 첫 번째 child인지 아닌지에 따라 child 또는 sibling으로 설정해 fiber 트리에 추가한다.

  // 2. create new fibers
  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++;
  }


3. 다음 작업 단위를 검색한다. child-> sibling -> uncle 같은 순서로 시도해 실행한다.

  // 3. return next unit of work
  if (fiber.child) {
    return fiber.child;
  }
  let nextFiber = fiber;
  while (nextFiber) {
    if (nextFiber.sibling) {
      return nextFiber.sibling;
    }
    nextFiber = nextFiber.parent;
  }

 

전체 performUnitOfWork 함수는 다음과 같다.

function performUnitOfWork(fiber) {
  // 1. add dom node
  if (!fiber.dom) {
    fiber.dom = createDom(fiber);
  }

  if (fiber.parent) {
    fiber.parent.dom.appendChild(fiber.dom);
  }
  
  // 2. create new fibers
  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++;
  }

  // 3. return next unit of work
  if (fiber.child) {
    return fiber.child;
  }
  let nextFiber = fiber;
  while (nextFiber) {
    if (nextFiber.sibling) {
      return nextFiber.sibling;
    }
    nextFiber = nextFiber.parent;
  }
}

 

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

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 render(element, container) {
  nextUntilOfWork = {
    dom: container,
    props: {
      children: [element],
    },
  };
}

let nextUntilOfWork = null;

function workLoop(deadline) {
  let shouldYield = false;
  while (nextUntilOfWork && !shouldYield) {
    nextUntilOfWork = performUnitOfWork(nextUntilOfWork);
    shouldYield = deadline.timeRemaining() < 1;
  }
  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 4 완료!

728x90

댓글