ReactElement는 Virtual DOM(이하 VDOM)이라는 콘셉트의 구현체입니다. VDOM은 in-memory에 존재하는 자바스크립트 객체이며 React.createElement 함수로 생성할 수 있습니다. 이를 통해 생성된 엘리먼트는 일반적인 자바스크립트 객체입니다. 이를 아주 단순하게 표현하면 아래 코드 예시와 같습니다.

React.createElement = (tag, config, children) => {
    //
    return {
        tag: tag,
        className: config.className,
        props: {
            children: children
        },
        dom: null
    }
};

React.createElement("div", {className: "root-layout"}, 
                    [React.createElement("h1", {className: "title"}), 
                     React.createElement("h5", {className: "sub-title"})]
                   );

기존의 DOM은 노드의 변화가 생길 때마다 DOM Tree 전체에 대한 연산을 다시 해야만 했습니다. React는 VDOM에서 필요한 노드에 대한 연산만 행한 후에  진짜 DOM을 한 번에 변경합니다. 또한 사용자가 DOM Tree 탐색을 하면서 노드를 핸들링해야 하는 수고를 덜어 줍니다.

실제 DOM에 VDOM을 반영하는 ReactDOM.render 함수를 간단히 표현하면 다음과 같습니다.

ReactDOM.render = (virtualDom, realDom) => {
    //
    realDom.appendChild(virtualDom.dom);
};

DOM 노드가 트리에 삽입되는 것을 Mount라 하며 그 과정을 대략적으로 묘사하면 다음과 같습니다.

React.mountElement = (element, parentDomNode) => {
    //
    const domNode = document.createElement(element.tag);
    element.dom = domNode;

    if (element.className !== undefined) {
        domNode.className = element.className;
    }

    parentDomNode.appendChild(domNode);
};

단순한 ReactElement는 State를 갖지 않습니다. React에서는 State 관리가 가능한 Component 클래스를 제공합니다. Component 클래스는 대략 아래와 같은 인터페이스를 가집니다.

class Component {

    constructor(props)
        //
        this.props = props | {};
        this.state = {};
    }

    setState(partialState) {
        // merge states and update component.
    }

    render() {
        // will be overridden.
    }
}

Component의 constructor 메서드는 Component가 생성된 후, Mount 이전에 불립니다. 이 메서드에는 컴포넌트를 초기화하는 작업이 포함되어 있는데 그 중 props는 컴포넌트 외부로부터 파라미터로 받아오는 값이기 때문에 constructor 함수의 파라미터로 넘겨 주어야 합니다. 이를 상속한 리액트 컴포넌트들에서도 마찬가지로 constructor 메서드를 제대로 작동시키기 위해서는 아래 예시와 같이 파라미터로 props를 전달하여 실행해야 합니다.

class UserSpecifiedComponent extends React.Component {

    constructor(props) {
        super(props);
        // do something you wanna do after component created.
        // for example, initialize state.
        this.state = {
          name: 'default name'
        }
    }
}

setState 메서드는 아래 작성된 코드와 같이 변경된 state의 값 또는 바뀐 state의 값을 반환하는 함수 객체를 파라미터로 받습니다. 그리고 shouldComponentUpdate 메서드에서 변경 사항을 비교한 뒤에 컴포넌트를 업데이트합니다. shouldComponentUpdate 메서드에 대한 내용은 후술합니다.

setState(partialState) {
    //
    const prevState = this.state;
    let changedPartialState = null;
  
    if (typeof partialState === 'function') {
        changedPartialState = partialState(prevState, this.props);
    } else {
        changedPartialState = partialState;
    }

    const nextState = Object.assign({}, prevState, changedPartialState);

    if (shouldComponentUpdate(this.props, nextState)) {
        updateComponent();
    }
}

React 도큐먼트에서는 constructor 메서드에서 state를 초기화하고 그 이외에는 setState 메서드를 이용해서 state를 변경할 것을 권장합니다. 그 이유는 setState에서 수행하는 내용을 알면 이해할 수 있습니다.
constructor 이외의 장소에서 state 변수를 직접 변경할 경우, 명시적으로 컴포넌트를 업데이트하는 메서드를 호출하지 않는 이상(forceUpdate() or this.setState()) 리렌더링이 일어나지 않아 변화가 가시적으로 반영되지 않습니다.
constructor에서 직접 변수를 조작하는 이유는 constructor가 render 메서드보다 먼저 불리기 때문에 render를 다시 호출해야 할 필요가 없기 때문입니다. 또한 setState 메서드가 비동기로 동작하기 때문에 constructor가 호출된 후 최초 render 호출 시기에 state 초기화가 되어 있지 않습니다.

React Comonent는 Lifecycle Method들을 제공합니다. Lifecycle Method란 컴포넌트 상태 변화에 따라 호출되는 추상 메서드입니다. 우리는 이를 재정의하여 특정 상태 주기에서 수행할 행위를 작성할 수 있습니다.
React Component의 생명주기는 크게 Mount-Update-Unmount로 나뉩니다.

상기한 바와 같이 Mount란 VDOM 노드를 DOM Tree 에 삽입하는 것을 말합니다. Unmount은 반대로 트리에서 노드가 빠지는 것입니다. Update는 예상하신 대로 노드의 변화를 반영하는 것입니다.

Component Lifecyle 인터페이스를 정리하면 다음과 같습니다.

    componentDidMount() {
      /**
     * Component가 Mount된 후에 바로 호출됩니다. 해당 메서드를 오버라이드하여 상태를 변경하면 다시 렌더링됩니다. 
     */
    }
    
    shouldComponentUpdate(nextProps, nextState, nextContext) {
      /**
     * props나 state의 변화로 다시 리렌더링이 결정되었을 때 호출됩니다.
     * `Component` 클래스는 항상 true를 반환합니다.
     * `PureComponent` 클래스는 props와 state에 대하여 shallow comparison*을 한 후 그 결과를 반환합니다.
     * 해당 메서드에서 false를 반환할 경우 렌더링이 일어나지 않습니다.
     */
    }
    
    componentWillUnmount() {
      /**
     * 컴포넌트가 DOM 트리에서 빠지는 즉시 호출됩니다. 
     * 네트워크 요청을 취소하거나 componentDidMount에서 생성한 데이터를 정리하는 등의 필요한 후처리 작업을 기술합니다. 
     */
    }
    
    componentDidCatch(error, errorInfo) {
    /**
     * Exception을 잡아 핸들링합니다.
     */
}
  • shallow comparison이란 equality를 확인하는 것입니다. 숫자나 문자열 같은 값을 비교할 때는 값을 비교하지만 객체를 비교할 때는 그들의 attribute를 비교하지 않고 레퍼런스만 비교합니다. shouldComponentUdpate를 임의의 조건에 따라 false를 반환하도록 작성할 경우, 불필요한 리렌더링을 줄일 수 있습니다.

고맙습니다.

React + MobX
SPA 라이브러리인 React를 MobX state 관리 라이브러리와 함께 배워봅시다.