http://wit.nts-corp.com/2014/11/19/2584
센스있는 개발자라면 기술 관련 위클리 메일을 하나정도는 받아보고 있을 것이다. 나 역시 많은 위클리 메일을 구독하고 있다. 무슨 위클리가 그리 많은지… 이젠 부담스러워서 몇 개 구독을 끊을까 생각중. 아무튼 매주 쏟아지는 정보 중 10%도 못 읽고 넘기고 있지만 JavaScript 위클리는 꼬박꼬박 챙겨보려고 애쓰고 있다. 뭔가 JavaScript로 밥 벌어 먹고 사는 사람의 의무 같다고나 할까? 그래서 주말 아침 잠자리에서 눈 뜨면 습관적으로 JavaScript 위클리를 훑어본다.
요즘들어 위클리 메일에 부쩍 이름이 자주 보이는 녀석이 하나 있는데 바로 React다. 물론 어떤 대상을 누군가 자주 언급한다고 해서 그것이 반드시 좋은 것이라고 할 수는 없다. 그냥 궁금하다. 뭐 하는 녀석인지. 왜 자꾸 내 눈 앞을 왔다갔다하는지. 그래서 공식 사이트에 들어가서 훑어봤다.
응…? 그런데 이 녀석 재밌다. 그래서 React가 뭐하는 녀석인지 알아보기 위해서 간단한 예제를 가지고 겉을 한 번 핥아보려고 한다.
React를 한 문장으로 정의를 하자면,
‘페이스북이 웹 UI를 개발할 때 사용하려고 만든 자바스크립트 라이브러리라’고 할 수 있다.
Backbone이나 Angular를 접해봤거나 또는 라이브러리와 프레임워크를 민감하게 구분하는 사람이라면 React가 라이브러리라는 사실에 의아해 할 수도 있다. 그렇다. React는 프레임워크가 아닌 라이브러리다. React의 관심사는 단순하다. 오로지 View. 여기에서 말하는 View는 MVC의 각 컴포넌트 중 View에 대응하는 정도로 이해하면 맞다.
React의 특징은 크게 세 가지를 꼽을 수 있다.
이게 뭔가 싶을텐데 예제 없이 아무리 떠들어봐야 감이 잘 오지 않는다. 그래서 간단한 예제를 만들면서 React가 어떤 녀석인지 살펴볼 생각이다.
준비한 예제는 초간단 Spinbox 컴포넌트다. 행여 Spinbox가 뭔지 모르는 사람이 있더라도 상관없다. 이 친구의 얼굴을 보고나면 ‘아하’ 할 테니까.
그렇다. 엄청 단순하고 허접해 보이는 Spinbox다. Spinbox가 화려해봐야 얼마나 화려하겠냐만은. React가 어떤 녀석인지 살펴보는 게 이 글의 목적이기 때문에 최대한 단순한 예제를 선택했다.
아래는 요구사항 목록이다.
만들고자 하는 게 무엇인지 알았으니 다음 할 일은 실습환경을 구성하러 출발.
설치 하는 방법이 너무 쉬워서 이걸 굳이 이 글에서 설명해야하나 싶다. 하지만 아직 React를 주제로 하는 한글 자료가 없는 상황이라 영어 울렁증을 가진 분들을 위해 설치 방법을 설명하고 넘어가겠다. 이정도는 네가 말 안 해줘도 안다!! 싶으신 분은 그냥 넘어가면 된다.
설치는 매우 간단하다.
아래 링크를 타고가서 직접 다운로드를 받거나,
CDN URL을 이용해서 스크립트 태그로 바로 불러올 수 있다.
react-0.12.0.js와 JSXTransformer-0.12.0.js 파일을 불러오는데, 두 파일이 하는 역할은 뒤에서 설명한다.
나는 좀 더 메트로 섹슈얼 모던 프런트엔드 개발자스럽고 싶은 사람은 bower를 이용한다.
이게 끝이다.
Spinbox를 만들기 전에 워밍업 차원에서 화면에 Hello World를 찍어보자. 물론 React를 이용한다. 아래에 있는 코드를 index.html 파일에 작성한다.
index.html
브라우저로 열어보면 아래와 같이 Hello, World!가 뜨는 걸 볼 수 있다.
오…? 이게 뭐야. 신기하다. JavaScript 코드 안에 HTML 마크업을 그대로 작성해서 넣었는데 문법 오류가 발생하지 않다니. 무슨 짓을 한거지?
방금 작성한 코드 중에 아래 부분을 다시 한 번 살펴보자.
HTML 마크업 코드를 JavaScript 코드에 넣으려면 문자열로 만들어야하지 않았던가? 이렇게.
누가 무슨 짓을 한거지?
비밀은 header에서 불러오는 JSXTransformer.js 파일에 있다. 눈치 빠른 사람이라면 이름으로 대충 어떤 원리인지 감을 잡았을 것이다. 이 녀석은 아래처럼 타입이 text/jsx인 script 태그안에 사용자가 작성한 내용을 브라우저가 실행할 수 있는 JavaScript 코드로 변환을 한다.
개발자 도구로 Elements 탭을 확인해보면 JSXTransformer가 변환한 JavaScript 코드를 확인할 수 있다.
JSX는 JavaScript + XML이라는 뜻으로, 간단히 말해서 기존 XML을 허용하는 JavaScript 확장 문법이다. 위에서 본 것처럼 React는 JSX를 지원함으로써 개발자가 JavaScript에 마크업 코드를 직접 작성할 수 있게 허용한다.JavaScript 코드에 HTML 코드를 문자열로 넣어본 적이 있는 사람이라면 개행을 표현하는 게 얼마나 귀찮은 일인지 잘 알 것이다. JSX는 단순히 XML을 허용하는 것에 그치지 않고, JavaScript 변수나 프로퍼티의 값을 바인딩 엘리먼트에 바인딩하는 약간의 템플릿스러운 기능도 제공한다. 다만, 다소 엄격한 문법 체계를 가지고 있어 너그러운 브라우저에 익숙해있던 개발자라면 좀 짜증날 수도 있다.
JSX에 대한 상세 설명은 http://facebook.github.io/jsx/ 에서 볼 수 있다.
JSX를 사용할 경우 JSXTransformer를 이용해서 컴파일 과정을 거쳐야한다. 위에 있는 예제에서 처럼 JSXTransformer를 header에서 로딩하여 런타임 시에 컴파일하는 방법이 있고, 코드 작성 시점에 npm 커맨드 라인 툴을 watch(변경이 일어나는지 지켜보고 있다가, 변경이 발생하면 자동으로 컴파일 ) 방식으로 컴파일 하는 방법도 있다.
JSX를 사용할지 말지는 개발자의 선택일 뿐 react는 강제하지 않는다. 쓰기 싫으면 안 써도 상관없지만 어떤 장점이 있는지 살펴봐야 할 테니 여기에서는 사용하는 쪽을 택했다(그런데 안 쓸거면 굳이 React로 개발할 이유가 없잖아…?).
그럼 이제 본격적으로 Spinbox를 만들어보자. 첫 번째 요구사항부터 볼까.
우선 Spinbox 마크업이 필요할 것 같으니 마크업 코드를 작성하자.
끝.
간단하다. 그래서 ‘초간단’이다. 작성한 마크업을 React를 이용해서 렌더링 할 수 있게 React.render 안으로 옮긴다.
index.html
브라우저로 파일을 열어서 아래와 같은 그림이 나오면 성공이다. 크롬에서 보이는 모습을 찍은 스크린샷이므로 다른 브라우저에서 보이는 모습은 이와 다를 수 있다.
잘 나오는 걸 확인했으면 다음으로 넘어가자.
HTML 안에 script 코드를 작성한 게 마음에 걸리니 별도의 js 파일로 분리하자. src/spinbox.react.js 파일을 하나 만들어서 그 안으로 type=”text/jsx” 안의 코드를 모두 옮겼다.
src/spinbox.react.js
index.html
분리는 쉽게 했지만 이게 다가 아니다. 페이지에 하나 이상의 Spinbox를 넣을 수 있다는 요구사항을 충족하려면 이 녀석을 충돌없이 재사용할 수 있게 코드를 모듈화해서 컴포넌트로 만들어야 한다. 지금은 그냥 단순히 HTML 페이지에서 JavaScript 코드를 분리해놓았을 뿐이다.
React.createClass 함수를 이용하면 코드를 모듈화 한 React 컴포넌트를 쉽게 만들 수 있다. 코드 모양새를 보면 jindo.class와 유사하다.
React.createClass 함수에 전달하는 첫번째 인자인 객체 리터럴에 정의한 내용을 토대로 React 컴포넌트가 만들어진다. 이렇게 작성한 컴포넌트는 마치 HTML5 WebComponent처럼 사용할 수 있다.
이것이 바로 가상 DOM(Virtual DOM)이다. React는 사용자가 정의한 컴포넌트를 가상 DOM으로 만들어서 쉽고 간편하게 재사용 할 수 있는 방법을 제공한다. <Spinbox /> 라니… 쪼렙 개발자인 나한테는 그저 신기하다.
이 코드의 동작 방식을 간단히 설명하자면, React.render는 첫번째 인자로 전달받은 가상 DOM이 가리키는 객체를 생성해서 render 함수를 호출한다. 그 다음에 render가 돌려준 결과값을 두번째 인자로 전달한 #example 엘리먼트에 삽입한다. 물론 내부에서 더 많은 일이 일어나겠지만 지금은 이 정도만 언급하고 다음으로 넘어가자.
첫번째 요구사항을 처리하기 위해 페이지 안에 Spinbox 컴포넌트 두 개가 들어가는지 테스트 해 볼까?
결과는…
응…?
기대와 달리 Spinbox가 하나 뿐이다.
뭐지…?
열심히 구글링을 해서 확인을 해봤는데, React는 대상 엘리먼트 안에다가 컴포넌트를 추가하는 게 아니라 채워넣는다. 따라서 같은 대상 엘리먼트에 컴포넌트를 생성하면 만들 때마다 내용을 덮어쓴다. 추가할 수 없을까 싶어서 여기저기 찾아봤는데 방법이 없다. 뭔가 심오한 철학이 있는 것 같기도 한데 아직은 확신할 수 없으니 그건 다음에 언급하기로 하고.
컴포넌트를 여러 개 삽입하고 싶다면 몇 가지 방법으로 우회(?)를 해야한다.
1) 대상 엘리먼트를 달리 해주거나,
2) render 할 때 상위 엘리먼트로 한 번 더 감싸준다거나 (까다롭게 <Spinbox />만 두 개 넣으면 에러를 뱉는다)
3) 아니면 여러 개의 컴포넌트를 가지고 있는 상위 컴포넌트를 만들어야 한다.
첫번째 요구사항은 이걸로 해결했으니 다음으로 넘어가자.
React는 JSX를 이용해서 HTML 엘리먼트에 어트리뷰트 값을 지정하는 것과 같은 방식으로 컴포넌트의 초기 값을 설정할 수 있다.
가상 DOM이라는 이름을 생각해 보면 자연스러운 모습이다. 그렇다고 기존 스타일을 완전히 버린 것은 아니어서 아래와 같이 객체 리터럴을 초기값으로 전달할 수도 있다.
이렇게 전달한 값은 자동으로 해당 컴포넌트 객체의 props에 저장되어서 원할 때 this.props의 프로퍼티에 접근해서 참조할 수 있다.
자, 다시 요구사항으로 돌아와서 초기값을 200으로 설정하고 결과를 확인하자.
결과가 의심스러우면 값을 변경해가면서 제대로 작동하고 있는지 확인해도 좋다. 두 번째 요구사항도 간단히 처리했다.
세 번째 요구사항으로 넘어가자. 사용자가 아무런 값을 지정하지 않았을 때 기본 값을 지정하는 게 이번 미션이다.
React 컴포넌트는 this.props 외에 this.state라는 프로퍼티를 가지고 있다. React는 컴포넌트를 생성할 때 getInitialState 함수의 리턴값을 this.state에 할당한다.
위와 같은 코드가 있을 경우 this.state는 { value : 200 }이고, 이 값은 나중에 this.state.value로 가져올 수 있다.getInitialState 함수에서 state.value에 사용자가 전달한 this.props.value를 할당한다. 이 때 this.props.value가 비어있으면 기본값인 200을 할당한다. 그리고 이 this.state.value를 기본값으로 Spinbox를 화면에 그리는 코드를 아래와 같이 작성했다.
여기서 의문이 생긴다. props는 뭐고, state는 뭐지? 왜 props를 사용하지 않고 state를 사용한거지? 이 둘은 왜 구분해야하는걸까?
React는 컴포넌트 객체의 상태를 props와 state, 2가지로 구분해서 관리한다. React 문서에 나온 내용에 따르면 props는 컴포넌트를 생성할 때 사용자가 지정하는 일종의 설정 값이다. 이 값은 외부에서 넘겨받아 컴포넌트를 초기화 할 때 사용한다. 한 번 지정하고 나면 이 값은 변경할 수 없다. 물론 JavaScript 언어 특성상 코드 레벨에서 변경은 가능하지만 개발자 도구 콘솔에 경고 메시지가 뜨는 걸 볼 수 있다. 즉, 초기 설정 값을 보관하는 용도인 셈이다.
이에 반해 state는 단어의 뜻 그대로 컴포넌트의 현재 상태를 나타낸다. 내부에서 어떤 변경이 일어나거나, 화면에 현재 보여지는 상태가 변경되었을 때 필요한 값은 state에 관리한다. state의 가장 큰 특징 중 하나는 DOM에 자동으로 바인딩 된다는 점이다. 사용자가 state의 어떤 값을 변경하면 React는 변경 사항을 DOM에 적용해 상태를 동기화한다. 좀 더 자세한 내용은 뒤에서 입력 이벤트 처리를 할 때 살펴보자.
JavaScript라는 언어의 특성상 이러한 제약을 강제하지는 못하지만(ES5의 Object라면 가능하겠지만…), 변해야하는 값과 변하지 말아야하는 값을 분리해서 관리하는 매커니즘을 제공함으로써 개발자가 고민해야하는 사항을 하나 덜어줬다는 점에서 긍정적이다. 좀 더 깊게 파고들어보면 뭔가 다른 심오한 철학이 더 있을 것 같지만 지금은 가볍게 살펴보는 걸로 만족하고 다음 단계로 넘어가자.
이번에는 값 증가, 감소 기능을 구현하자. 요구사항에서 ‘이벤트’의 냄새가 강하게 난다. 그렇다. 버튼을 클릭하면 값을 증가시키고, 증가한 값을 화면에 출력하는 게 이번에 구현해야 할 기능이다. React는 아주 간단한 이벤트 바인딩 인터페이스를 제공한다. Angular로 인해서 이제는 식상해졌지만.
감소 버튼에 이벤트 처리 함수를 바인딩하자. 아직 함수는 만들지 않았지만 이름은 decrease로 할 생각이다. 그냥 진행하다보니 이렇게 됐을 뿐, 순서는 의미없다.
사용자가 감소 버튼을 클릭하면 컴포넌트 객체의 decrease 함수가 불린다.프론트엔드 개발자들이 아주 싫어하는 Event Model 1 방식과 닮았다.
한 가지 재밌는 점이 있는데 React는 모든 이벤트를 이벤트 딜리게이션으로 처리한다. 컴포넌트를 렌더링 할 때 가장 상위 엘리먼트에 하나의 이벤트 리스너만 등록한 다음에 이 함수에서 모든 이벤트를 감지해서 적절한 이벤트 처리 함수를 호출하는 방식이다.
다시 예제로 돌아와서 있지도 않은 함수를 엘리먼트에 달아놓을 수는 없으니 this.decrease를 구현하자. this.state.value 값을 1 감소 시키는 게 decrease가 하는 일의 전부다.
this.setState 메소드에 주목하자.state에 있는 어떤 값을 변경할 때는 state 객체에 직접 접근하는 게 아니라 React가 제공하는 setState 메소드를 이용한다. 그냥 직접 this.state.value의 값을 변경할 수도 있지만, setState 함수를 이용해서 값을 변경하면 해당 프로퍼티가 바인딩 되어있는 HTML Element의 값을 React가 자동으로 갱신해준다. React 공식 문서는 이를 Reactive State, 반응형 상태라고 부른다.
양방향 데이터 바인딩을 제공하는 Angular와 달리, React의 데이터 바인딩은 단방향이다. 데이터는 오직 한 곳에 존재하며 뷰는 컴포넌트의 현재 상태를 사용자에게 보여줄 뿐이다. 데이터 바인딩을 단순하게 함으로써 성능을 높이고, 디버깅을 쉽게 할 수 있다는 게 React가 가지고 있는 철학중 하나다. 이 지점에서 조금 어려운 이야기인 Mutable or Immutable이 나오는데, 이 글의 범위를 벗어나므로 이 내용은 다음 기회에 정리를… 할 수 있을까? 뭐 어쨌든.
노 트1 : 예제를 작성하다가 한 가지 문제를 발견했다. state를 input 엘리먼트의 value 바인딩하면 React는 자동으로 input 요소에 사용자가 값을 입력할 수 없게 막아버린다. 그래서 사용자 입력을 허용할 생각이라면 직접 state를 이용한 자동 바인딩을 사용해서는 안 된다. 이걸 어떻게 뚫을 수 있을까 찾아봤는데 아직 해결책을 못 찾았다. 혹시 좋은 방법 아시는 분은 언제든지 공유해주시면 감사.
노 트2 : ES5까지는 어떤 객체 프로퍼티의 상태 변경을 감지할 수 있는 방법을 언어 레벨에서 제공하지 않기 때문에 이런 식으로 별도의 Proxy 함수를 만들어서 사용한다. ES6의 Proxy, ES7에 Object.observe가 대중화되면 이런 것도 좀 더 단순해지겠지.
같은 방법으로 증가 버튼을 클릭 이벤트에 increase 메소드를 바인딩하자.
예제 자체가 너무 간단하다는 점을 감안하고 봐도 React의 이벤트 바인딩은 참 간단하다. 그리고 Event Model1 방식을 허용함으로써 어떤 엘리먼트에 어떤 이벤트를 할당했는지 쉽게 알아 볼 수 있는 것도 장점이다. 이 방법이 마음에 들지 않는다면 직접 DOM 엘리먼트를 셀렉팅해서 이벤트를 바인딩해도 상관없다. 선택은 당신의 몫.
마지막으로 유효성 체크를 구현하면 끝이다.
adjustValue 함수를 만들어서 값의 범위를 조절하게 함으로써 모든 요구사항을 처리했다.
아직은 이 녀석이 뭔지 잘 모르겠다.
겉핥기 용으로 준비한 예제를 가지고 정말 겉만 핥는 동안에 많은 생각이 머리 속에 떠올랐다. 정리는 잘 안 된다. 좀 더 봐야 알 것 같다. 그래도 뭔가 마무리 이야기는 해야할테니 막연한 느낌 두 가지를 꺼내보면,
이정도?
이게 뭐야 싶겠지만… 지금은 곤란하니 조금만 기다려달라. 더 파헤쳐볼테니까.
정말 그냥 기본만 본 거라서 React가 뭐다라고 말하기는 아직 섣부른 것 같다. 판단을 쉽게 하지 않는 내 성격 탓이기도 하고. 이 글을 어떻게 마무리를 지어야 할지 지금 참 난감하다. 다음에는 머리 속에 맴도는 어지러운 생각을 정리하는 글을 써야겠다. 글이 길어질 것 같아 간만보고 뒤로 미룬 이야기들까지 더해서.
ps. “기다릴 수 없어, 나는 지금 몹시 궁금해” 싶은 사람은 아래 링크로 달려가시길. 재밌는 글이 많다.