2018년 상반기 스터디 - react.js 2주-2

Posted by 서동우 on March 25, 2018

4장. 순수 리액트

  • 일반적으로 jax(javascript as XML)을 사용하지 않고 react를 만드는 경우는 거의 없고 홈페이지에서도 jax 를 사용하는 것을 권장함
  • 리액트가 내부적으로 어떤 식으로 동작하는 파악하기 위해서 순수 리액트에 대해서 알아봄

4.1 페이지 설정

  • 리액트를 다루기 위해서는 기본적으로 React, ReactDOM 라이브러리를 불러와야 함
  • React : 뷰를 만들기 위한 라이브러리
  • ReactDOM: UI를 DOM에 실제 렌더링할 때 사용하는 라이브러리
  • 기존에는 합쳐져 있었으나 0.14 때 분리

4.2 가상 DOM(Virtual DOM)

  • HTML은 브라우저가 문서 객체 모델(DOM)을 구성하기 위해 따라야 하는 절차
  • HTML element –> 브라우저 –> DOM element –> 사용자가 볼 수 있다.
  • 랜더링이란?

    • MODEL을 통해서 화면을 만들어내는 과정
    • DOM을 이용해서 브라우저에 화면을 출력해주는 과정
    • reflow
      • 랜더 트리를 기반으로 각 노드가 화면에 정확한 위치에 표시되도록 배치하는 과정
    • repaint
      • reflow를 통해서 배치된 노드들을 가로지르며 그리는 과정으로 visibility, outline, background-color 와 같은 시각적 속성에 해당 정보를 입히는 과정
    • webkit 엔진
    • Gecko 엔진
  • 이런 부분을 다 신경써서 프로그램을 해야 성능 문제를 해결…
  • 가상 DOM을 사용할 경우 가상 DOM을 변경할 경우 최적화 되는 방법을 찾아서 React에서 자동으로 DOM을 변경해준다.(자세한 건 뒤에 다시..)

4.3 리액트 엘리먼트

  • DOM 엘리먼트와 비슷해보지만 생긴건 완전히 다름.
  • 리액트 엘리먼트는 그 에 대응하는 실제 DOM 엘리먼트가 어떻게 생겨야 하는지를 기술
React.createElement('h1', null, '구운 연어');   //첫 번째 인자 엘리먼트의 타입, 두 번째 인자는 프로퍼티, 세 번째 인자는 자식 노드 
// 결과는 <h1>구운 연어</h1>
React.createElement('h1', {id: 'test', 'data-type': 'title'}, '구운 연어');   
// 결과는 <h1 data-reactroot id="test" data-type:"title">구운 연어</h1>
// data-reactroot는 이 element가 root elemenet 라는 것을 나타낸다.
// 생성된 element
{
    $$typeof: Symbol(React.element),
    "type": "h1",
    "key": null,
    "ref": null,
    "props": {"children": "구운 연어"},
    "_owner": null,
    "_store": {}
}

4.4 ReactDOM

  • 리액트 엘리먼트를 브라우저에 렌더링하는 데 필요한 모든 도구가 들어 있다.
  • 이번 스터디를 하면서는 redner 만 거의 사용할 듯.
    const header = React.createElement('h1', null, '구운 연어');
    ReactDOM.render(header, document.getElementById('react1'));
    

4.5 자식

  • ReactDOM은 항상 한 엘리먼트만 DOM으로 렌더링할 수 있다.
  • 리엑트 props.children 을 사용해 자식 엘리먼트를 렌더링한다. 이럴 경우 해당 작식 엘리먼트들은 부모 엘리먼트와 트리 구조로 구성된다. ==> 컴포넌트 트리
<ul class="ingredients">
    <li>연어 500그램</li>
    <li> 1</li>
    <li>버터 상추 2</li>
    <li>옐로 스쿼시 1</li>
    <li>올리브 오일 1/2</li>
    <li>마늘 3</li>
</ul>

React.createElement(
    'ul',
    {className: 'ingredients'},
    React.createElement('li', null, '연어 500그램'),
    React.createElement('li', null, '잣 1컵'),
    React.createElement('li', null, '버터 상추 2컵'),
    React.createElement('li', null, '옐로 스쿼시 1개'),
    React.createElement('li', null, '올리브 오일 1/2컵'),
    React.createElement('li', null, '마늘 3쪽')
);

{
    $$typeof: Symbol(React.element),
    "type": "ul",
    "key": null,
    "ref": null,
    "props": {"children": [
        {tpye: 'li', props: {'children': '연어 500그램'} ... },
        {tpye: 'li', props: {'children': '잣 1컵'} ... },
        {tpye: 'li', props: {'children': '버터 상추 2컵'} ... },
        {tpye: 'li', props: {'children': '옐로 스쿼시 1개'} ... },
        {tpye: 'li', props: {'children': '올리브 오일 1/2컵'} ... },
        {tpye: 'li', props: {'children': '마늘 3쪽'} ... },
    ]},
    "_owner": null,
    "_store": {}
}

4.6 데이터로 엘리먼트 만들기

  • 리액트는 단순한 자바스크립트이기 떄문에 자바스크립트에서 사용할 수 있는 모든 함수를 사용 가능
  • 데이터와 구현 부분을 분리해서 표시가 가능
  • 배열을 이터레이션해서 자식 엘리먼트의 리스트를 만드는 경우 key 값을 생성하도록 권장함

var items = [
    '연어 500그램',
    '연어 500그램',
    '잣 1컵',
    '버터 상추 2컵',
    '옐로 스쿼시 1개',
    '올리브 오일 1/2컵',
    마늘 3
];

React.createElement(
    'ul',
    {className: 'ingredients'},
    items.map( (item, i) => React.createElement('li', {key: i}, item))
);

4.7 리엑트 컴포넌트

  • 모든 UI는 여러 가지 부분으로 이루어진다.
  • React에서는 이러한 부분을 컴포넌트라고 말한다.
  • props 를 이용해서 UI를 만들 때 사용하는 로직과 데이터를 분리할 수 있음
  • 컴포넌트를 구성할 수 있는 방법은 총 3가지가 있다.

    1. React.createClass
      • 가장 오래전부터 사용한 방법
      • 조만간 없어 질 것이라고 함.
          const IngredientsList = React.createClass({
              displayName: 'IngredientsList',
              render() {
                  return React.createElement(
                      'ul',
                      {className: 'ingredients'},
                      React.createElement('li', null, '연어 500그램'),
                      React.createElement('li', null, '잣 1컵'),
                      React.createElement('li', null, '버터 상추 2컵'),
                      React.createElement('li', null, '옐로 스쿼시 1개'),
                      React.createElement('li', null, '올리브 오일 1/2컵'),
                      React.createElement('li', null, '마늘 3쪽')
                  );
              }
          });
        
          const list = React.createElement(IngredientsList, null, null);
        
          ReactDOM.render(list, ...);
        
    2. React.Component
      • ES2015+ 에서 추가된 class를 이용하는 방법
      • React.Component 는 추상화 클래스로 해당 클래스를 상속받아서 render() 를 구현하는 방식으로 사용할 수 있다.
        
          const IngredientsList = class extends React.Component{
              render() {
                  return React.createElement(
                      'ul',
                      {className: 'ingredients'},
                      React.createElement('li', null, '연어 500그램'),
                      React.createElement('li', null, '잣 1컵'),
                      React.createElement('li', null, '버터 상추 2컵'),
                      React.createElement('li', null, '옐로 스쿼시 1개'),
                      React.createElement('li', null, '올리브 오일 1/2컵'),
                      React.createElement('li', null, '마늘 3쪽')
                  );
              }
          };
        
          const list = React.createElement(IngredientsList, null, null);
        
          ReactDOM.render(list, ...);
        
    3. 상태가 없는 함수형 컴포넌트
      • 객체가 아니고 함수로 표현
      • 될 수 있으면 해당 방식을 사용하도록 추천
       const IngredientsList = props => React.createElement(
                       'ul',
                       {className: 'ingredients'},
                       React.createElement('li', null, '연어 500그램'),
                       React.createElement('li', null, '잣 1컵'),
                       React.createElement('li', null, '버터 상추 2컵'),
                       React.createElement('li', null, '옐로 스쿼시 1개'),
                       React.createElement('li', null, '올리브 오일 1/2컵'),
                       React.createElement('li', null, '마늘 3쪽')
                   );
      

4.8 DOM 랜더링(Reconciliation)

  • react에서 render() 함수를 호출할 경우 각각 새로운 React elements Tree가 생성이 된다.
  • 바로 이전에 생성된 Tree와 비교를 해서 가장 효율적인 방법을 사용해서 UI를 업데이트한다.
  • 기본적인 tree 비교 로직은 O(n^3)의 시간 복잡도를 가지고 있기 때문에 비효율적이다.
  • 그래서 아래와 같이 2가지의 가정을 가진 휴리스틱 알고리즘을 사용해서 O(n)에 근사하도록 구현을 했다.
    1. 다른 타입의 두 엘리먼트는 다른 트리를 만들 것이다.
    2. 각 렌더링에서 유지되는 엘리먼트에 key 프로퍼티를 통해 같은 엘리먼트라는 것을 알린다.

Diffing 알고리즘

2개의 트리를 비교하는 알고리즘

  1. 타입이 다른 Elements

    • 지우고 다시 새로 element를 추가
    • 부모가 틀리고 자식은 같더라도 그냥 다 지우고 다시 추가한다.
  2. 동일한 타입의 DOM Elements

    • 속성이 틀린 경우에 속성만 변경한다.
  3. 동일한 타입의 React Elements

    • 인스턴스 자체는 변하지 않는다.
    • 기존 인스턴스의 프로퍼티를 새 인스턴스로 변경을 하고, 기존 인스턴스의 componentWillReceiveProps(), componentWillUpdate() 함수 실행
    • render() 함수를 호출하고 이전 element와 다음 elements에 해당 알고리즘을 제귀로 돌린다.
  4. 자식 재귀

    • 기본적으로 DOM 노드의 자식을 재귀할 때는 동시에 전/후 리스트를 확인해서 차이점을 비교한다.
    • 마지막노드를 추가할 경우에는 그냥 마지막 노드만 추가된 것으로 인식한다.
    • 하지만 첫 번째 노드가 추가될 경우 각 리스트의 값들이 틀리기 때문에 기존에 element를 수정하게 된다.
    • 이럴 경우 효율성에 문제가 생긴다.
    • 위 효율의 문제를 개선하기 위해서 key 값을 추가되었다.
    • 자식이 키를 가지고 있다면, react는 key 값을 비교해서 전/후 리스트를 확인하고 차이점을 비교한다.

4.9 팩토리

  • h1 element 같은 것은 ReactDOMFactories.h1 을 이용해서 쉽게 만들 수 있다.
  • 하지만 jax 를 사용하면 어짜피 의미 없는 것이라…. 그냥 이런게 있다고만 알고 있으면 될 듯

참고 문헌

  • 러닝 리액트
  • https://reactjs.org/docs/react-without-jsx.html
  • https://reactjs.org/docs/refs-and-the-dom.html
  • http://d2.naver.com/helloworld/59361
  • https://github.com/nhnent/fe.javascript/wiki/Reflow%EC%99%80-Repaint
  • http://taligarsiel.com/Projects/howbrowserswork1.htm
  • http://www.phpied.com/rendering-repaint-reflowrelayout-restyle/
  • https://reactjs.org/docs/reconciliation.html
  • http://meetup.toast.com/posts/110