[우아한 테크러닝] React&TypeScript 3회차 - React 만들어보기

minidoo·2020년 9월 9일
1

우아한 테크러닝

목록 보기
5/6
post-thumbnail

React의 등장 배경

const list = [
  { title: 'React에 대해 알아봅시다.' },
  { title: 'Redux에 대해 알아봅시다.' },
  { title: 'TypeScript에 대해 알아봅시다.' }
];

const rootElement = document.getElementById("root");

function app() {
  rootElement.innerHTML = `
    <ul>
      ${list.map(item => `<li>${item.title}</li>`).join("")}
    </ul>
  `;
}

app();

list라는 이름으로 만든 배열을 자바스크립트로 화면에 그리는 과정을 코드로 작성했다.
위의 코드는 innerHTML을 이용하여 Element를 생성하여 직접 넣어주고 있다.

이를 '순수 함수'로 변경해보자.
( 순수함수 : 동일한 인자를 넣었을 때 항상 동일한 리턴값을 반환하고 외부에 영향 받지 않는 함수 )

function app(items) {
  rootElement.innerHTML = `
    <ul>
      ${items.map(item => `<li>${item.title}</li>`).join("")}
    </ul>
  `;
}

app(list);

app 함수는 innerHTMLDOM에 직접 접근하여 구조를 변경한다.
이런 DOM을 Real DOM이라 부르는데, Real DOM은 Low Level이라 복잡도가 매우 높다.
따라서, 리얼 돔을 직접 다루는 것은 프로젝트 규모가 커질수록 성능 저하로 이어질 수 있다.

문제를 해결하기 위해 나온 것이 React이다.
브라우저는 HTML을 DOM이라는 트리구조로 converting 하여 자바스크립트에서 Dom Tree 로 접근한다.

하지만, 사용자 관점에서는 이것도 매우 까다로운 구조이다.
React은 Real DOM을 Virtual DOM 으로 converting하여 사용자에게 좀 더 쉬운 구조를 제공한다.
이때 자바스크립트가 Virtual DOM으로 접근하는데서 JSX가 등장한다.


React 기초

import React from 'react';
import ReactDOM from 'react-dom';

function App() {
  return (
    <div>
      <h1>Hello?</h1>
      <ul>
        <li>React</li>
        <li>Redux</li>
        <li>Mobx</li>
        <li>TypeScript</li>
      </ul>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));

React 문법을 사용하여 간단한 코드를 작성해보았다.

ReactDOMrender라는 정적메소드로 두 개의 인자를 받는다.
첫 번째 인자는 화면에 렌더링할 컴포넌트이며, 두 번째 인자는 컴포넌트를 렌더링할 요소이다.

위의 ul 태그 내부 요소를 컴포넌트로 분리해 만들 수도 있다.

import React from 'react';
import ReactDOM from 'react-dom';

function StudyList() {
  return (
    <ul>
      <li>React</li>
      <li>Redux</li>
      <li>Mobx</li>
      <li>TypeScript</li>
    </ul>
  )
}

function App() {
  return (
    <div>
      <h1>Hello?</h1>
      <StudyList />
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));

각 기능을 컴포넌트로 분리해 컴포넌트에 이름을 부여할 수 있다는 것은 매우 좋다.
코드가 간단해질 뿐만 아니라 함수의 기능을 직관적으로 알 수 있다.

function StudyList() {
  return (
    <ul>
      <li className="item">React</li>
      <li>Redux</li>
      <li>Mobx</li>
      <li>TypeScript</li>
    </ul>
  )
}

className을 사용하여 태그에 속성을 부여할 수도 있다.
JSX에서 className을 사용하는 이유는 class 속성이 자바스크립트 예약어이기 때문이다.

HTML에서는 class, JSX에서는 className을 사용하는 것을 기억하자!


Virtual DOM (가상돔)

babel을 통해 JSX가 실제로 동작하는 방법을 살펴보자.
babel : 최신 사양의 자바스크립트 코드를 ES5 이하의 코드로 변환(트랜스파일링)

// JSX

function StudyList() {
  return (
    <ul>
      <li>React</li>
      <li>Redux</li>
      <li>Mobx</li>
      <li>TypeScript</li>
    </ul>
  )
}
// babel

function StudyList() {
  return _react.default.createElement("ul", null, 
    _react.default.createElement("li", null, "React"), 
    _react.default.createElement("li", null, "Redux"), 
    _react.default.createElement("li", null, "Mobx"), 
    _react.default.createElement("li", null, "TypeScript"));
}

createElement를 사용하여 ul > li 요소를 만들어주는 방식으로 동작한다.
첫 번째 인자값에 '태그 타입', 두 번째 인자 값에 '속성' 그리고 세 번째 인자 값에 '자식 요소'가 들어있다.

자바스크립트에서 가장 다루기 쉬운 것은 '객체(Object)'이다.
따라서 StudyList에서 반환하는 JSX Element들을 객체로 만들어서 다룰 수 있다.

const vdom = {
  type: 'ul',
  props: {},
  children: [
    { type: 'li', props: { className: 'item' }, children: 'React' },
    { type: 'li', props: { className: 'item' }, children: 'Redux' },
    { type: 'li', props: { className: 'item' }, children: 'Mobx' },
    { type: 'li', props: { className: 'item' }, children: 'TypeScript' }
  ]
}

jsx 이름 설정하기

// JSX

/* @jsx name */
function App() {
  return (
    <div>
      <h1>Hello?</h1>
    </div>
  );
}

const app = App();
// babel

"use strict";

@jsx createElemnt
function App() {
  return name("div", null, createElemnt("h1", null, "Hello?"));
}

var app = App();

주석으로 @jsx name을 달면 함수의 이름을 설정할 수 있다.
babel로 변경된 App 함수의 return 값에 'name'라는 이름이 생겼다.

이제 '객체' 속성을 이용하여 React를 직접 만들어보자.
React에 구현되어 있는 createElement 함수는 아래와 같다.

/* @jsx createElement */

function createElement(type, props={}, ...children) {
  return { type, props, children };
}

function App() {
  return (
    <div>
      <h1>Hello?</h1>
    </div>
  );
}

console.log(<App/>);

createElement 함수는 태그 타입, 속성, 자식 요소를 받는다.
App 함수는 @jsx로 createElement를 사용하게 되고 Virtual DOM 객체를 만든다.

const vdom = createElement("div", null, createElement("h1", null, "Hello?"));

앞에서 객체 구조로 만들었던 vdom과 같은 모양이다.
위의 내용을 통해 <App />은 런타임이 아닌 '컴파일 타임'에서 돌아간다는 것을 알 수 있다.

다른 점은 type에 태그 타입이 아닌 function App() 함수가 들어가 있다.
사용자가 return한 함수의 첫 문자가 대문자면 컴포넌트라고 인식하도록 만들어졌기 때문이다.

/* @jsx createElement */

function createElement(type, props={}, ...children) {

  if(typeof type === 'function') {
    return type.apply(null, [props, ...children]);
  }

  return { type, props, children };
}

function App() {
  return (
    <div>
      <h1>Hello?</h1>
    </div>
  );
}

console.log(<App/>);

createElement 함수에 분기를 사용하여 코드를 수정해주면 type에 태그 타입이 들어간 결과가 나온다.
이제 앞에서 객체 구조로 만들었던 vdom과 완전히 같은 모양이 되었다.

즉, React는 DOM을 다루기 쉬운 Object로 변환해서 사용하는 것이다.

createElement로 생성된 객체를 실제 DOM으로 구성하는 render 함수는 아래와 같다.

/* @jsx createElement */

// 2. 작성된 객체를 Real DOM으로 구성한다.
function renderElement(node) {
  if(typeof node === 'string') {
    return document.createTextNode(node);
  }
  const el = document.createElement(node.type);

  node.children.map(renderElement).forEach((element) => {
    el.appendChild(element);
  });

  return el;
}

// 3. Real DOM을 리턴 받아 DOM에 넣어준다.
function render(vdom, container) {
  container.appendChild(renderElement(vdom));
}

// 1. createElement로 객체를 생성한다. - VDOM 요소를 리턴
// (아래의 예시 = 컴파일 타임에서 createElemnt로 트랜스파일링 한다.)
function createElement(type, props = {}, ...children) {
  if(typeof type === 'function') {
    return type.apply(null, [props, ...children]);
  }
  return { type, props, children };
}

function StudyList(props) {
  return (
    <ul>
      <li className="item">React</li>
      <li className="item">Redux</li>
      <li className="item">Mobx</li>
      <li className="item">TypeScript</li>
    </ul>
  );
}

function App() {
  return (
    <div>
      <h1>Hello?</h1>
      <StudyList />
    </div>
  )
}

console.log(<App/>);
render(<App />, document.getElementById("root"));

이는 Virtual DOM이 render 함수를 거쳐 Real DOM이 되는 과정을 표현한 것이다.

props를 매개변수로 넘겨받는 컴포넌트도 동일하게 동작한다.

function Row(props) {
  return <li>{props.label}</li>
}

function StudyList(props) {
  return (
    <ul>
      <Row label="test" />
      <li className="item">React</li>
      <li className="item">Redux</li>
      <li className="item">Mobx</li>
      <li className="item">TypeScript</li>
    </ul>
  );
}

React의 컴포넌트와 상태 관리

React 컴포넌트는 클래스 컴포넌트와 함수 컴포넌트로 작성할 수 있다.

클래스 컴포넌트

클래스 컴포넌트는 라이프사이클이 존재하며, 특정 요소가 업데이트되면 필요한 부분만 호출해서 바꾼다.

import React from 'react';
import ReactDOM from 'react-dom';

class Hello extends React.Component {
  constructor(props) {
    super(props);
    
    this.state = {
      counter: 1
    };
  }

  componentDidMount() {
    this.setState({ count: this.state.count + 1 });
  }

  render() {
    return <h1>React Class!!</h1>;
  }
}

ReactDOM.render(<Hello />, document.getElementById('root'));

클래스 컴포넌트는 상태를 변경하기 위해 setState 메소드를 사용한다.
클래스 컴포넌트는 생성자에 의해 초기화되기 때문에 일반적인 접근 방법으로는 상태를 변화시킬 수 없다.

// 잘못된 코드
this.state.counter = 2;

// 올바른 코드
this.setState({ count: this.state.count + 1 });

클래스 컴포넌트에서는 new 연산자로 객체 인스턴드를 만든다.
리액트는 객체를 만들고 render 함수로 DOM에 붙여주는 과정을 모두 해준다.

// const hello = new Hello();
// hello.render();

ReactDOM.render(<Hello />, document.getElementById('root'));

함수형 컴포넌트

과거 함수형 컴포넌트는 라이프사이클이 없었다.
즉, 함수 컴포넌트의 상태는 함수가 호출될 때마다 생성되기 때문에 유지될 수 없었다.

그러나 함수 컴포넌트도 Hook가 등장하며 상태 관리가 가능해졌다.
상태 관리를 위해 Hook에서 제공하는 useState를 사용한다.

import React, { useState } from 'react';
import ReactDom from 'react-dom';

class Hello extends React.Component {
  constructor (props) {
    super(props);
    this.state = { count: 1 }
  }

  componentDidMount () {
    this.setState({ count: this.state.count + 1 })
  }
  render () {
    return <h1>React Class!!</h1>;
  }
}

function App () {
  const [ counter, setCounter ] = useState(0);

  return (
    <div>
      <Hello />
      <h1>React Function!!</h1>
      <button onClick={() => setCounter(counter + 1)}>증가</button>
      { counter }
    </div>
  );
}

ReactDom.render(<App />, document.getElementById('root'));

useState[0]는 '상태값'이 useState[1]에는 'dispatcher'로 사용되는 함수가 들어있다.
위의 예제처럼 button에 클릭 이벤트를 주면 dispatcher을 사용해 상태를 변경할 수 있다.

setCounter이 이전 값을 기억하고 있다가 값을 증가시키는 것이다.

Virtual DOM에선 최상위 루트 요소는 하나밖에 올 수 없다.
그렇기 때문에 div로 감싸서 최상위 요소를 하나로 만들어준다.

Hook은 최상위에서만 호출해야한다.
이는 컴포넌트가 렌더링 될 때마다 항상 동일한 순서로 Hook이 호출되는 것을 보장한다.
이러한 점은 useState가 여러 번 호출되는 중에도 Hook의 상태를 올바르게 유지할 수 있게 해준다.

즉, 상태 관리가 가능해진 것이다.

0개의 댓글