스무디 한 잔 마시며 끝내는 리액트 + TDD - 1장 리액트란?

728x90

먼저 갑자기 리액트??? 하고 의문을 품을 여러 지인들에게 상황을 간략히 설명하자면

네 제가 그 5명 중 하나입니다.

뭐 엄청나게 상세하게 정리하려는 것은 아니고 각 장별로 기록하고 싶은 부분을 기록해나가면서 넘어가려고한다.
(나중에 어드민 페이지 정도는 리액트로 만들어줘야 "오 프론트도 쫌 할줄아는데?" 소리한번 들어보는 것을 목표로...)

자바스크립트의 역사

리액트는 자바스크립트(JavaScript, 이하 js) 언어를 기반으로 동작하는 라이브러리이다. 따라서 리액트를 이해하기 위해서는 기본적으로 js에 대한 이해가 필요하다. js의 역사를 간략히 훑으면서 리액트가 탄생한 이유를 이해해보자!

(js의 탄생 배경은 한번쯤은 들어봤을 수도 있다. 이번 기회에 해당 영상을 통해서 알아보는 것도 좋을 것 같다)

js는 '넷스케이프 커뮤니케이션즈 '(Netscape Communications)라는 회사에서 만들었다. 넷스케이프 커뮤니케이션즈는 정적인 HTML을 동적으로 표현하기 위한 경량 프로그래밍 언어를 도입하기로 하고, 새로운 프로그래밍 언어 개발에 착수하게 되는데, 이때 탄생한 것이 js이다.

더보기

이 당시에 넷스케이프는 시장 점유율이 약 90%에 달했다고 한다.

js의 기반이 되는 언어인 모카(Mocha)는 Brendan Erich가 10일만에 만들었으며, 이는 1993년 3월 넷스케이프 커뮤니케이션즈의 웹 브라우저인 넷스케이프 네비게이터2(Netscape Navigator 2)에 처음 탑재된다. 그리고 그해 9월 라이브스크립트(LiveScript)로 이름이 변경되고 최종적으로 12월에 자바스크립트(JavaScript)로 명명된다.

js는 많은 언어로부터 영감을 받아 만들어졌다. 변수 스코프(Scopre)와 클로져(Closure) 등의 규칙은 리스프(Lisp) 언어에서 가져왔으며, 프로토타입 상속은 스몰토크(Smalltalk)에서 파생된 언어인 셀프(Self) 프로그래밍 언어에서 영감을 얻었다. 넷스케이프 커뮤니케이션즈는 그 당시 MS와 경쟁하기 위해 썬 마이크로시스템즈(Sun Microsystems)와 협업하고 있었는데 자신들의 새로운 언어가 그 당시 인기 있었던 썬 마이크로시스템즈의 자바 문법과 유사하길 희망했다.

js의 이름의 유례는 다양한데 그 중 유력한 설은 넷스케이프 커뮤니케이션즈가 마케팅을 위해 당시 인기있었던 '자바'의 이름을 차용하여 '자바스크립트'라는 이름을 지었다는 것이다. 조금 더 자세히 설명하면, 자바스크립트는 자바에서 파생되었다는 인상을 주어 많은 프로그래머가 js를 사용하도록 유도하기 위함이었다는 것이다.

넷스케이프 네비게이션 브라우저가 흥행하자 js 언어도 크게 유행하게 된다. 이를 경쟁사인 MS가 가만히 있을 수 없지 않겠는가? MS는 js와 호환을 할 수 있는 J스크립트(Jscript)라는 언어를 개발하여 내놓는다. 이 J스크립트는 인터넷 익스플로러 3.0에 처음 도입되면서 js와 경쟁 구도를 가지게 된다.

이렇게 J스크립트를 사용하는 인터넷 익스플로러와 js를 사용하는 넷스케이프 네비게이션이 브라우저의 시장을 공유하기 시작하면서 크로스 브라우징(Cross Browsing) 이슈가 발생하기 시작했다. 넷스케이프 커뮤니케이션즈는 js에 새로운 스펙을 추가하면서 자신들의 브라우저에 새로운 기능을 추가하였고, MS는 J스크립트에 새로운 스펙을 추가하면서 새로운 기능을 추가하기 시작했다. js와 호환이 되던 J스크립트는 독자적인 스펙들이 더해지면서 점점 호환되지 않게 되었다. 호환되지 않은 두 브라우저가 브라우저 시장을 공유하면서, 개발자들은 넷스케이프 네비게이션과 인터넷 익스플로러에서 동작하는 웹 페이지를 개발하는 데 큰 어려움을 겪기 시작한다.

넷스케이프 커뮤니케이션즈는 이런 크로스 브라우징 문제를 일으키는 js의 파편화를 방지하고 모든 브라우저에서 동일하게 동작하는 표준화된 js에 대한 필요성을 느꼈으며, 컴퓨터 시스템의 표준을 관리하는 비영리 표준화 기군인 ECMA 인터내셔널에 자바스크립트 표준화를 요청하게 된다.

ECMA 인터내셔널에서는 1997년 1월, ECMA-262라 불리는 표준화 된 자바스크립트 초판(ECMAScript 1)의 명세서를 완성하지만, 자바스크립트의 상표권 문제로 인해 ECMAScript로 명명하게 된다. 우리가 흔히 알고있는 ECMAScript가 이렇게 탄생했다.

하지만 비영리 표준화 기구의 표준화된 명세서는 큰 힘이 없었으며, 크로스 브라우징 이슈는 여전히 존재하였다. 또한 js와 J스크립트를 사용한 브라우저의 돔(DOM, Document Object Model) 조작은 너무나 복잡하고 불편했다.

이 문제를 해결한 것이 jQuery이다. jQuery는 당시 가지고 있던 크로스 브라우징 이슈와 더불어 자바스크립트보다 배우기 쉽고 직관적인 API(Application Programming Interface)를 제공함으로써 선풍적인 인기를 끌게 된다. 지금까지도 jQuery는 돔을 다루는 방식에서 가장 쉽고 효율적인 방식으로 인정받고 있으며, 웹 브라우저에서 사실상 표준으로 오랜 기간 사랑받았다.

웹 서비스에서 jQuery가 크게 사랑을 받던 중에 아이폰, 안드로이드가 등장하면서 애플리케이션(Application)이라는 개념이 대중적으로 크게 확장되었다. 또한, 사용자들이 사용하는 단말기(PC, 스마트폰 등)의 성능이 크게 향상되었다. 이 때문에 그동안 웹 페이지라는 개념이었던 웹 서비스에도 웹 애플리케이션(Web Application)이라는 개념과 이에 대응하는 서비스들이 쏟아져 나오기 시작했다.

2010년, 구글은 이런 웹 애플리케이션 트렌드에 대응하고자 AngularJS라는 웹 애플리케이션 프레임워크를 출시하게 된다. AngularJS는 웹 서비스에 싱글 페이지 애플리케이션(SPA, Single Page Application)이라는 새로운 시대를 열게 된다 AngularJS는 MV(Model-View-Whatever), 양방향 데이터 바인딩(Two-way Data Binding) 등 웹 애플리케이션에 새로운 개념들을 많이 도입하였다. 하지만, 그 당시 AngularJS는 jQuery를 기반으로 하고 있었으며, 싱글 페이지 애플리케이션을 모두 다루는 프레임워크로써 너무 많은 변화와 새로운 개념으로 많은 개발자가 쉽게 접근하기 어려운 러닝 커브(Learning curve)를 안겨주었다.

2011년, 페이스북 개발자였던 Jordan Walke가 PHP용 HTML 컴포넌트 프레임워크였던 XHP에 영감을 받아 리액트를 개발하게 된다. 이렇게 개발된 리액트는 2011년 페이스북의 뉴스피드에 처음 적용하게 되고 이후 2012년 인스타그램닷컴에 적용된다. 페이스북은 2013년 5월 JSConf US에서 리액트를 오픈 소스로 발표하면서 리액트의 역사가 시작된다.

리액트는 자바스크립트 프레임워크였던 앵귤러(Angular)와 다르게 UI 자바스크립트 라이브러리로 출시된다. 싱글 페이지 애플리케이션의 거의 모든 부분을 담당했던 앵귤러와 다르게 리액트는 UI(User Interface)에 집중한 라이브러리로 출시하게 된다.

거의 새로운 언어에 가깝던 앵귤러와 달리 리액트는 js에 HTML을 포함하는 JSX(JavaScript XML)라는 간단한 문법과 양방향 데이터 바인딩이 가지는 문제점을 보완하고자 단방향 데이터 바인딩(One-way Data Binding)을 채택하였다. 그리고 가상 돔(Virtual DOM)이라는 새로운 개념으로 큰 인기를 끌게 된다.

리액트는 싱글 페이지 애플리케이션의 UI를 만드는 js 라이브러리이다. 그러므로 싱글 페이지 애플리케이션 프레임워크였던 앵귤러보다 러닝 커브가 낮다. 하지만 프레임워크가 아닌 라이브러리이므로 부족한 부분들이 존재하고, 이런 부분들을 채우기 위해서는 다른 라이브러리들과 함께 사용해야 한다. (ex. 리액트는 페이지 전환의 기능을 지원하지 않으므로 react-router 같은 다른 라이브러리를 사용해야 함)

리액트의 특징

리액트는 가상 돔(Virtual DOM)과 같은 새로운 개념과 다른 프레임워크와 달리 단방향 데이터 바인딩을 사용하는 등 리액트만의 특징을 가진다.

가상 돔

리액트는 가상 돔이라는 개념으로 웹 퍼포먼스 향상에 새로운 접근 방식을 제안하였고, 이를 통해 웹 애플리케이션의 성능을 극대화하였다. 가상 돔이 웹 퍼포먼스의 성능을 어떻게 향상시키는지 이해하기 위해서는 우선 브라우저에서 HTML, CSS가 렌더링(Rendering) 되는 부분을 이해할 필요가 있다.

image

브라우저가 네트워크를 통해 HTML을 전달받으면 브라우저의 렌더 엔진은 HTML을 파싱하여 돔 노드(DOM Node)로 이뤄진 트리를 만든다. 또한, CSS파일과 각 엘리먼트의 인라인 스타일을 파싱하여 스타일 정보를 가진 새로운 스타일 렌더 트리를 만든다.

이렇게 렌더 트리가 생성되면 브라우저는 Attachment라는 과정을 통해 스타일 정보를 계산하게 된다. 렌더 트리는 모든 노드는 attach라는 메서드를 가지고 있는데, Attachment 과정에서 이 메서드가 호출하게 되며 해당 메서드는 스타일 정보를 계산하고 결과값을 객체 형태로 반환하게 된다. 이 과정은 동기적으로 작동하며 만약 새로운 렌더 트리에 새로운 노드가 추가되면 해당 노드의 attach 메서드가 실행된다.

image

렌더 트리는 Attachment 과정을 거친 후 레이아웃이라는 과정을 거치게 된다. 레이아웃 과정에서는 브라우저가 렌더 트리와 각 노드에 좌표를 부여하고 정확히 어디에 어떻게 표시되는지 결정하게 된다.

마지막으로, 브라우저는 각 노드에 paint() 메서드를 호출하여 렌더링된 욧들에 색상을 입히는 Painting이라는 과정을 거친 후 최조적으로 화면을 표시하게 된다.

이렇게 화면에 표시된 후 자바스크립트를 사용하여 돔을 조작하게 되면 각 노드에 좌표를 계산하고 부여하는 레이아웃 과정이 다시 수행되며, 그 이후 색상을 입히는 페인팅 과정이 다시 수행되게 된다. 여기서 레이아웃 과정이 다시 수행되는 것을 리플로우(Reflow)라 하며 페인팅 과정을 다시 수행하는 것을 리페인트(Repaint)라고 한다. 이 리플로우와 리페인트는 돔의 각 노드에 관해 많은 연산을 수행하므로 이 과정을 수행하게 되면 웹 서비스의 성능 이슈가 발생한다.

물론 정적인 웹 사이트나 화면을 구성하는 돔에 변경이 적은 웹 사이트면 크게 문제가 되지 않지만, 싱글 페이지 애플리케이션처럼 돔 변경이 동시다발적으로 빈번히 발생하는 사이트인 경우에는 리플로우와 리페인트를 많이 수행되면 사이트의 성능 이슈가 발생하게 된다.

리액트는 리플로우와 리페인트의 문제를 해결하기 위해 화면에 표시되는 돔과 동일한 돔을 메모리상에 만들고, 돔 조작이 발생하면 메모리상에 생성한 가상 돔에서 모든 연산을 한 후 실제 돔을 갱신하여 리플로우와 리펜인트의 연산을 최소화하였다.

즉, 리액트는 가상돔을 통해 리플로우와 리페인트를 최소화하여 성능 최적화를 하였다.

단방향 데이터 바인딩

SPA의 대표적인 프레임워크 프레임워크인 Angular, Vue는 양방향 데이터 바인딩을 사용한다.

image

) (출처: 이 책의 저자분의 블로그)

양방향 데이터 바인딩을 사용자 UI의 데이터 변경을 감시하는 Watcher와 js 안에서 변경되는 데이터를 감시하는 Watcher를 통해 UI와 프로그램 안에 데이터를 자동으로 동기화해주는 시스템이다. 이를 통해 프로그래머는 js 내에 데이터 변경과 사용자 UI에서 데이터 변경 및 동기화를 크게 신경쓰지 않고 프로그램을 작성할 수 있다.

양방향 데이터 바인딩은 자동으로 데이터를 동기화해주는 장점이 있는 반면에 데이터 동기화를 위해 데이터 하나에 두 개의 Watcher가 사용되기 때문에 오버스펙일 경우가 발생할 수 있다. 또한, 수많은 Watcher에 의해 반대로 성능 저하가 발생할 수 있다. 앵귤러는 이런 오버 스펙과 많은 Watcher에 의한 성능 저하를 방지하기 위해 단방향 데이터 바인딩을 지원하고 있다.

리액트양방향 데이터 바인딩이 가지는 문제점과 복잡성을 피하고자 단방향 데이터 바인딩을 채택하고 있다.

image

단방향 데이터 바인딩은 단 하나의 Watcher가 js의 데이터 갱신을 감지하여 사용자의 UI 데이터를 갱신한다. 사용자가 UI를 통해 데이터를 갱신할 때는 양방향 데이터 바인딩과 다르게 Watcher가 아닌 Event를 통해 데이터를 갱신하게 된다. 이처럼 하나의 Watcher를 사용하기 때문에 양방향 데이터 바인딩에서 발생하는 문제들을 해결할 수 있으며, 더 확실하게 데이터를 추적할 수 있다.

또한, 리액트는 이런 단방향 데이터 바인딩과 더불어 Flux라는 개념을 도입하여 데이터의 흐름이 한쪽으로만 진행되도록 하고 있다.

JSX

리액트에서는 JSX라는 독특한 문법을 가진다. 이 문법 때문에 많은 js 개발자들이 큰 혼란을 겪지만, 다른 프로그래밍 언어를 조금이라도 다뤄 봤다면 쉽게 이해할 수 있다.

JSX는 js와 HTML을 동시에 사용하며, HTML에 js 변수들을 바로 사용할 수 있는 일종의 템플릿 언어(Template language)이다.

const App = () => {
  const hello = 'Hello world!';
  return <div>{hello}</div>;
};

리액트의 JSX를 사용하여 화면에 "Hello world!"를 출력하는 코드이다. js 변수인 hello를 HTML, 태그인 div 안에 {hello}로 사용하여 출력하고 있음을 확인할 수 있다. js라는 틀 안에서 보면 굉장히 이상한 코드이지만, 다른 언어들의 템플릿 언어를 생각하면 조금 이해가 될 것이다.

예를 들아, jsp에서는 아래와 같은 코드가 쓰인다. (jstl이라고 부른다)
<div><%= hello %></div>

이미 우리는 많은 언어에서 템플릿 언어를 사용하고 있다. 이처럼 JSX도 js 일종의 템플릿 문법이라고 기억하면 조금 더 쉽게 이해할 수 있을 것이다.

선언형 프로그래밍

프로그래밍은 크게 명령형 프로그래밍선언형 프로그래밍으로 구별할 수 있다. 명령형 프로그래밍은 어떻게(How)에 집중하고 선언형은 무엇(What)에 집중하여 프로그래밍한다.

// 명령형 프로그래밍 
const double = (arr) => {
  let results = [];
  for (let i = 0; i < arr.length; i++) {
    results.push(arra[i] * 2);
  }
  return results;
}

// 선언형 프로그래밍 
const double = (arr) => {
  return arr.map((elem) => elem * 2);
}

두 함수는 같은 동작을 하는 자바스크립트 함수이다. 첫 번째는 명령형 프로그래밍으로 작성된 함수이며, 두 번째 함수는 선언형 프로그래밍으로 작성된 함수이다. 이렇게 명령형 프로그래밍은 과정을 중심으로 프로그래밍을 하게 된다.

반면에, 선언형 프로그래밍은 map 함수를 사용하여 주어진 배열 값을 두 배로 만들어 반환하였다. map이 어떻게 동작하는지는 크게 신경 쓰지 않고 결과인 배열 값을 두 배로 만들기에 집중하여 프로그래밍하게 된다.

두 프로그래밍의 결과값은 같지만 명령 프로그래밍은 그 값을 얻기 위해 어떻게 하는지에 집중하고 있는 것을 알 수 있다. 그리고 선언형 프로그래밍은 js의 기본 제공 함수인 map을 사용하여 결과값이 무엇인지에 집중하고 있다. 이처럼, 라이브러리나 프레임워크 등으로 비선언형 부분을 캡슐화함으로써 명령형 프로그래밍 언어로 선언형 프로그래밍을 할 수 있다.

리액트에서는 특히 JSX를 사용함으로써 더욱 명확하게 선언형 프로그래밍을 활용하고 있다.

<script>
  var arr = [1, 2, 3, 4, 5]
  var elem = document.querySelector("#list");

  for (var i = 0; i < arr.length; i++) {
    var child = document.createElement("li");
    child.innerHTML = arr[i];
    elem.appendChild(child);
  }
}
</script>

앞의 예제는 js를 사용하여 HTML에 새로운 리스트를 추가하는 코드이다. 예제를 자세히 살펴보면 명령형 프로그래밍으로써 새로운 리스트를 표시할 li 태그를 작성한 후 js의 querySelector를 사용하여 표시할 위치를 가져오고, For문을 사용하여 리스트에 아이템을 하나씩 추가하고 있다.

리액트의 JSX 코드를 사용하면 아래와 같이 선언형으로 변환할 수 있다.

const arr = [1, 2, 3, 4, 5];
return {
  <ul>
    {arr.map((elem) => (
      <li>{elem}</li>
    ))}
  </ul>
};

리액트는 JSX라는 문법을 사용하기 때문에 위와 같이 HTML 안에서 map 함수를 사용하여 리스트에 아이템을 추가할 수 있다. 이처럼 리액트는 JSX를 활용하여 HTML을 조작할 때에도 선언형 프로그래밍을 할 수 있다.

이런 선언형 프로그래밍은 코드를 예측할 수 있게 하고 디버깅을 쉽게 할 수 있도록 도와주므로 전체적인 코드 퀄리티의 상승과 코드의 이해를 도와주는 효과를 얻을 수 있다.

컴포넌트 기반

리액트로 웹 UI를 개발할 때는 컴포넌트라고 불리는 작고 고립된 코드들을 이용하여 구현하게 된다.

const Title = () => {
  return <h1>Hello world</h1>
};

const Button = () => {
  return <button>This is a Button</button>;
};

const App = () => {
  return (
    <div>
      <Title />
      <Button />
    </div>
  );
};

앞의 예제처럼 리액트에서는 Title과 Button 컴포넌트를 만든 후에, App 컴포넌트에서는 이미 만들어진 UI 컴포넌트를 활용하여 페이지를 제작한다. 물론 Title 컴포넌트와 Button 컴포넌트는 다른 컴포넌트에서도 반복적으로 사용할 수 있다. 이처럼 리액트는 JSX를 활용하여 UI를 제작할 때 기본적으로 컴포넌트 기반 프로그래밍을 하게 된다.

앞으로 우리는 리액트의 JSX를 활용하여 필요한 컴포넌트를 제작하고 제작한 컴포넌트를 조합하여 화면을 구성하는 컴포넌트 기반 프로그래밍을 경험하게 될 것이다.

image

2장은 리액트 개발 환경을 만드는 것인데, 이는 이전에 리액트를 써본적이 있어서, 어느정도 다 환경이 다 형성돼있는 관계로 과감히 스킵하겠다. (책으로는 쭉 훑어보겠지만 기록은 안남기겠다 😉)

728x90