React 톺아보기 - 05. Reconciler_1

류지승·2024년 7월 30일

React

목록 보기
4/19
post-thumbnail

JSX로 작성한 컴포넌트들이 어떻게 React element로 변환되고, VDOM에 올리기 위해 어떤 방식으로 fiber로 확장하는지 알아보기

React element

Host Component - div / input / button / etc -> HTML element 요소
Custom Component - 우리가 가장 자주 쓰는 컴포넌트 Function / Class Component
Static Component - Fragment / lazy / Context / memo

export function createElement(type, config, children) { 
  /*...*/
}

type: 컴포넌트의 종류에 따라 다음의 값이 사용됩니다.

호스트 컴포넌트: 태그 이름
커스텀 컴포넌트: 작성한 함수
스태틱 컴포넌트: 미리 정의된 Symbol 또는 memo, lazy와 같이 함수 호출을 통해 만들어진 React element

config: props를 담고 있는 객체입니다.

children: 코드에서는 자식을 하나의 인자가 받도록 되어 있지만 실제로는 여러 자식이 올 경우 3 ~ n번째의 인자에 자식들이 들어옵니다. function createElement(type, config, children1, children2, ..childrenN)의 형태가 됩니다.

예시

// 우리가 자주 쓰는 jsx형태의 Component
const CustomComponent = () => '';

const App = () => 
<>
	<CustomComponent />
    <div id="host_component"></div>
</>

// Babel을 이용하여 Compnent -> React element로 바꾸기
const App = () => (
  React.createElement(
    React.Fragment, // type: Symbol.for('react.fragment')
    {}, // config
    React.createElement( // 첫번째 자식
      CustomComponent, // type: function
      {}, // config
      // 자식은 없음
    ),
    React.createElement( // 두번째 자식
      "div", // type: tag name
      { id: "host_component" } // config
      // 자식은 없음
    ) 
  );
)

// createElement가 무엇인가? 
const RESERVED_PROPS = {
  key: true,
  ref: true,
};

function createElement(type, config, children) { 
  /* 1 React element에는 key와 ref라는 예약된 속성들이 존재합니다. 
  config에 예약 속성을 제외한 나머지를 props 객체에 저장합니다. */
  let propName;

  const props = {};

  let key = null;
  let ref = null;

  if (config != null) {
    if (hasValidRef(config)) { // config.ref !== undefined;
      ref = config.ref;
    }
    if (hasValidKey(config)) { // config.key !== undefined;
      key = '' + config.key;
    }

    // 예약된 속성을 제외한 나머지를 props 객체에 저장
    for (propName in config) {
      if (
        /* 좀 더 쉽게 설명하기
        config 객체에서만 사용할 수 있는 propName이 존재하며, 
        hasOwnProperty.call(config, propName) 
        && RESERVED_PROPS(key랑 ref)가 아닌 거 */
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        // 해시테이블 형식으로 저장
        props[propName] = config[propName];
      }
    }
  }

  // 2 복수의 자식이 넘어온다면 하나의 배열에 저장합니다.
  // argument란? 함수 내부에 들어오는 모든 인수들 저장소(배열로 저장됨 )-> js에서 함수 객체에 사용되는 프로퍼티
  // 즉 arguments는 type, config, childern들이 저장되어 있는 배열인데, type이랑 config를 빼면 childrenLength가 됨.
  const childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    props.children = children;
  } else if (childrenLength > 1) {
    const childArray = Array(childrenLength);
    for (let i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    props.children = childArray;
  }

  // 3 default props가 존재할 경우 props 객체에 적용합니다.
  // 16 version function Component로 바뀌면서 
  // 잘 안 쓰임
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }

  // 4 1 ~ 3에서 작성한 정보를 가지고 React element 객체를 만듭니다.
  return ReactElement(
    type,
    key,
    ref,
    props,
    /*...*/
  );
}

Fiber

fibertag를 통해 판단한다.

const createFiber = function(
  tag: WorkTag, // fiber 고유(어떤 형식인지)
  pendingProps: mixed, // 바꿀 props 
  key: null | string, // 어떤 element인지 판단하는
  mode: TypeOfMode, // mode
): Fiber {
  return new FiberNode(tag, pendingProps, key, mode);
};

createFiberFromFragment는 포장지라고 생각하면 됨 prop가 존재하지 않고, children만 존재
TextReact Native에서 사용하는 것이므로 패스

createFiberFromElement()

export function createFiberFromElement(
  element: ReactElement,
  mode: TypeOfMode,
  expirationTime: ExpirationTime,
): Fiber {
  const type = element.type;
  const key = element.key;
  const pendingProps = element.props;
  const fiber = createFiberFromTypeAndProps(
    type,
    key,
    pendingProps,
    mode,
    expirationTime,
  );
  return fiber;
}

createFiberFromTypeAndProps

fiber가 어떤 fiber인 지 구분하는 함수

import {
  IndeterminateComponent,
  ClassComponent,
  MemoComponent,
  /*...*/
} from 'shared/ReactWorkTags'; // fiber tag
import {
  REACT_FRAGMENT_TYPE,
  REACT_MEMO_TYPE,
  /*...*/
} from 'shared/ReactSymbols'; // 스태틱 컴포넌트

export function createFiberFromTypeAndProps(
  type: any,
  key: null | string,
  pendingProps: any,
  mode: TypeOfMode,
  expirationTime: ExpirationTime,
): Fiber {
  let fiber;
  // IndeterminateComponent는 undefined과 같은 존재
  // fiber의 tag가 아직 아무것도 정해져 있는 상태
  let fiberTag = IndeterminateComponent; 
  let resolvedType = type;

  if (typeof type === 'function') {
    // class component
    if (shouldConstruct(type)) { // type.prototype && type.prototype.isReactComponent;
      fiberTag = ClassComponent;
    }
//else?? 여기서는 함수형 컴포넌트라고 단정지을 수 없습니다.
    
  // type이 string이면 호스트 컴포넌트 입니다. ex) 'div', 'input'..
  } else if (typeof type === 'string') {
    fiberTag = HostComponent;

  // 이하 스태틱 컴포넌트
  } else {
    getTag: switch (type) {
      case REACT_FRAGMENT_TYPE:
        return createFiberFromFragment(
          pendingProps.children,
          mode,
          expirationTime,
          key,
        );
      case REACT_CONCURRENT_MODE_TYPE:
        fiberTag = Mode;
        mode |= ConcurrentMode | BlockingMode | StrictMode;
        break;
      /*...*/
      default: {
        if (typeof type === 'object' && type !== null) {
          switch (type.$$typeof) {
            case REACT_MEMO_TYPE:
              fiberTag = MemoComponent;
              break getTag;
            case REACT_LAZY_TYPE:
              fiberTag = LazyComponent;
              resolvedType = null;
              break getTag;
            /*...*/
          }
        }
        let info = '';
        invariant(
          false,
          'Element type is invalid: expected a string (for built-in ' +
            'components) or a class/function (for composite components) ' +
            'but got: %s.%s',
          type == null ? type : typeof type,
          info,
        );
      }
    }
  }

  fiber = createFiber(fiberTag, pendingProps, key, mode);
  fiber.elementType = type;
  fiber.type = resolvedType;
  fiber.expirationTime = expirationTime;

  return fiber;
}

Fiber Root Node

function Todo() {
  const [todos, push] = useState([]);
  return (
    <>
      <Input submit={todo => push(todos => [...todos, todo])} />
      <OrderList list={todos} />
    </>
  );
};

function Input({ submit }) {
  const [value, setValue] = useState("");
  return (
    <>
      <input value={value} onChange={e => setValue(e.target.value)} />
      <button
        onClick={() => {
          submit(value);
          setValue("");
        }}
      >
        submit
      </button>
    </>
  );
}

function OrderList({ list }) {
  return (
    <ol>
      {list.map(item => (
        <li>{item}</li>
      ))}
    </ol>
  );
}

function App() { return <Todo /> };
ReactDOM.render(<App />, container);

profile
성실(誠實)한 사람만이 목표를 성실(成實)한다

0개의 댓글