원문보기

예전에 React와 Redux로 옮겨가면서 배운 몇 가지 교훈에 관한 포스트를 작성했었다.

그 뒤로 꽤 긴 시간이 지났지만 아직도 React와 그 친구들을 사용하고 있고, 더 공유해보고 싶은 것이 있다. 무엇이 가장 큰 고통이며 어떻게 그것을 이겨낼 것인가 하는 것 말이다. 그것은 바로 forms를 구현하는 것이다.

많은 웹 애플리케이션들은 폼 기반이다. 당신의 플랫폼을 사용자에게 제공하려는 목적으로 사용자로 하여금 폼을 채우고 폼 컴포넌트를 조작하게끔 한다. 곧, 폼 컴포넌트를 React에서는 어떻게 사용하고 작성해야 하는지에 대한 의문이 생긴다. 그리고 React와 Redux가 어떻게 함께 동작하는지도 말이다. 예를 들어 다음과 같은 의문들이다.

  • # 컴포넌트는 어떻게 동작해야 하는가?
  • # 데이터를 어디에 유지해야 하는가?
  • # 유효성 검사는 어떻게 수행하는가?
  • # 폼의 상태와 그 변화는 어떻게 탐지하는가?

Bizzabo에서는 꽤 많은 페이지들이 폼 기반이다. 이 페이지들은 꽤 오래전부터 해왔던 것이기 때문에 Backbone.js를 사용했던 동안 어떤 도전들이 있었고 어떤 가이드가 필요한지 알고 있었다. 그러나 React는 또다른 경기장이였고, 사용자가 수월하게 폼의 내용을 채우게 하기 위해서는 Backbone을 사용한 폼이나 React를 사용한 폼이나 차이점을 느끼지 못해도록 해야 한다. 그러나 한편으로는 이 모든 물음들에 대한 대답을 위해 폼을 사용하는 방법을 완전히 새롭게 설계했다.

이 물음들에 대답하기에 앞서, 결과부터 이야기하자면 우리는 redux-form을 사용했다. Erik Rasmussen에 의해 만들어진, redux 위에서 동작하는 폼 상태 관리 라이브러리로, 매우 잘 관리되고 있다.

# 컴포넌트가 어떻게 동작해야 하는가?

시스템 내의 모든 폼 컴포넌트는 사용자 인터페이스 동작을 가지고 있어야만 한다. 예를 들어 input을 보자면, 많은 상태를 가지고 있다: 유효한지, 터치되었는지, 포커스되었는지, 더티상태인지 등등. 당신은 이러한 모든 상태가 시각적으로 표현되기를 원할 것이다. 에러나 경고를 표시하는 등 말이다. 우리는 Redux-form의 솔루션을 선택했다. 이 솔루션은 컴포넌트의 상태에 대한 메타 정보를 제공한다. 과연 redux form 컴포넌트를 어떻게 사용할까? 컴포넌트는 어떻게 각각 개별적인 input과 어떻게 Redux 스토어를 연결시킬까?(다음 주제에서 이를 연마할 것이다)

<Field name={keyName} component={MyCustomInput} {...this.props} />

이를 활성화하기 위해서 해야할 것은 단 한가지다. 위 구문처럼 컴포넌트를 <Field /> 컴포넌트로 name과 함께 감싸는 것이다. 그러면 당신이 원하는 대로 컴포넌트는 UI 상태들을 표시할 수 있게 된다. redux form의 <Field />를 생성하는 여러 방법들이 있으니 이 문서를 참조하라.

따라서 이제 UI의 각 상태를 다음과 같이 추가할 수 있다.

const {meta: {error, touched, dirty}} = this.props;
<div>
     <input
       disabled={isDisabled}
       type={type || "text"}
       placeholder={placeholder || 'Enter a value'}
       name={name}
     />
</div>
{error && <label className="error">{error}</label>}
{touched && <label className="touched">{touched}</label>}
{dirty && <label className="dirty">{dirty}</label>}

위에서 보듯이 <Field />를 사용하면 공짜로 UI 상태를 얻을 수 있다. 그리고 상태에 따라 알맞은 알림을 표시할 수 있게 된다.

# 데이터를 어디에 유지해야 하는가?

주요 질문 중 하나는 데이터를 어디에 유지해야 하는 가이고 이에 대해 대답할 필요가 있겠다. 그 대답을 위해서 먼저 걱정거리들을 찾아보자: 모든 폼 요소들은 초기값을 가지고 있어야만 하고, 폼 중 하나는 “new”라는 상태를 가질 때 비워져야 하거나, “edit”라는 상태를 위한 값을 가져야할 수도 있다. blur 혹은 keyup과 같은 어떠한 변화(혹은 관심을 가져야할 만한 어떠한 이벤트)에서도 값이 변경되어야 한다. 그래서, 어디에 둔단 말인가?

Redux-form의 솔루션은 Redux reducer다. 이 reducer들은 redux-form의 액션 디스패치를 듣고서는 Redux 내의 폼 상태를 관리한다. 폼들은 “form” key name 내에 마운트된다. 그리고 모든 폼은 고유한 이름을 가지고 있어서 이를 form[name]을 통해 접근할 수 있다.

아래의 코드를 보면 쉽게 이해할 수 있다.

import { createStore, combineReducers } from 'redux'
import { reducer as formReducer } from 'redux-form'

const reducers = {
  event,
  account,
  user,
  form: formReducer     
}
const reducer = combineReducers(reducers)
const store = createStore(reducer)

redux-form은 초기값과 현재값을 위한 셀렉터를 제공한다. 이것을 사용하는 것이 훌륭한 이유는 그것이 바로 redux 자체라는 것이다. 특정 커스텀 액션을 듣고서 데이터를 그에 맞게 조작하는 것이다. redux-form만의 어떤 액션이 아니고 당신이 만든 특정 액션에 대해서도 말이다. 그저 플러그인 함수만 사용하면 된다.

지금까지는 컴포넌트의 현재 상태를 표시하는 것에 대해 이야기하고, 데이터를 관리하는 위치에 대해서 이야기했다.

그러나 데이터 자체를 다루는 것은 어떤가?

# 유효성 검사는 어떻게 수행하는가?

모든 폼들에서 가장 주요한 도전과제는 유효성 검사다. 사용자 입력을 제출할 때(혹은 그에 준하는 어떤 브라우저 이벤트든 간에) 폼 컴포넌트와 그 값이 업데이트 하기 충분하지 않을 때 값에 대한 유효성 검사가 필요해진다. 대답해야할 질문의 이슈는 다음과 같다: 폼 컴포넌트의 값을 상태에 두고 유효성 검사가 통과하면 스토어로 전달해야하나? 아니면 스토어에 있는 모든 데이터에 대해 유효성 검사를 수행해야 하나?

Redux form은 모두에 대한 대답을 한다. 유효성 검사는 라이브러리의 에코시스템 중 한 부분이다. redux form을 이용해 생성한 폼에 validation method를 전달할 수 있다. 혹은 특정한 <Field />에 대해서 (우리만의 특정한 버전으로부터)유효성 검사를 수행할 수도 있다. 1가지 제약 사항만이 존재한다. 바로 error 오브젝트의 구조다. 다음과 같다. { field1: <string>, field2: <string> }. 3rd 파티 라이브러리를 사용하거나 직접 validation 메서드를 구현할 수 있다. 우리는 후자를 택해서 validator.js를 사용한다. 이 라이브러리는 거대한 양의 유효성검사를 커버하고, 특별한 검사를 위한 플러그인 기능을 지원한다. 그리고 가장 중요한 것은 redux form이 요구하는 error 오브젝트를 반환한다.

const validate = (data, props) => {

    const rules = {
        'ga-id': ['regex:/^UA-\\d{4,10}-\\d{1,4}$/'],
        'input': 'max:50 | required'
    };

    const messages = {
        'max.input': 'This is my max value text',
        'required.input': 'This is my required text'
        'regex.ga-id': 'My Google Analytics id format text'
    };

    const validator = new Validator(data, rules, messages);
    validator.passes();
    return validator.errors.all();
};

validator.js를 사용한 유효성 검사

위 예제를 통해 유효성 검사를 살펴보자. 몇몇 검사를 각각 필드(예. input)에 제공할 수 있고, 각각 에러에 대한 텍스트를 제공할 수 있다. 그리고 검사를 한 번 수행한 다음 error 오브젝트를 반환하면 된다.

# 폼의 상태와 그 변화는 어떻게 탐지하는가?

우리의 마지막으로 풀어야할 큰 항목은 폼의 현재 상태에 대한 탐지와 변화에 대한 추적이다. 예를 들어보자. 우리가 원하는 것은 원래 값으로부터 변했는지 여부다. 사용자가 다른 곳으로 이동하기 전에 저장하지 않은 변경사항이 있는지 알려주기 위한 것이다. Redux Form은 현재 폼 상태에 대한 완전한 시야를 제공한다. dirty, pristine 등등과 심지어 사용자에 의한 touched 여부까지 말이다(해당 요소에 포커싱을 준 적이 있는지). 그리고 redux 액션을 통해 상태 변화를 유발하는 것을 허용하는 외부 API도 제공한다. 예를들어 폼을 제출할 때나, 혹은 폼을 리셋할 때나 redux 액션을 사용해서 실제 제출 버튼을 누를 필요가 없다면 실제 제출 버튼 없이도 이를 수행할 수 있다.

결론

폼은 언제나 까다롭다. 모든 폼들은 어떠한 요구사항이 있고 가끔은 회피하기 위한 솔루션이 필요할때도 있다. 하지만 대부분의 시나리오는 redux-form에 의해 커버할 수 있다. 그리고 그게 안된다면 당신만의 솔루션을 redux form으로 마이그레이션할 수 있다. 우리에게는 훌륭하게 동작하고 있으므로, 당신에게도 그렇게 되었으면 좋겠다.

Bizzabo에서 함께 일합시다! 합류하세요!

원문보기

개발자들은 Higher Order Components(HOC), Stateless Functional Components를 적용하고 있고 그럴만한 이유가 있다: 그것들이 개발자들의 열망 중 하나인 코드 재사용을 보다 쉽게 만들어주기 때문이다.

HOC와 Functional Stateless Components에 대한 많은 글들이 있다. 몇몇은 소개글이고, 몇몇은 깊이 파고드는 관점에서 서술된 것이다. 나는 기존 컴포넌트를 리팩토링해서 재사용가능한 엘레멘트를 만드는 관점에서 이 글을 쓴다.

당신은 아마도 코드 재사용이 과대 평가되었다고 생각할 것이다. 혹은 그게 너무 어렵다고 생각할 것이다. 특히 웹과 모바일 간에 코드를 공유하는 관점에서 말이다. 그러나 여기 몇 가지 고려해볼만한 장점이 있다:

  • UX 일관성. 디바이스 사이에서 혹은 애플리케이션 사이에서
  • 교차 보정 업그레이드하기: 사용되고 있는 모든 컴포넌트를 향상시키고 업데이트하는 일들
  • 라우팅과 인증 규칙들을 재사용하기
  • 라이브러리 전환(예를 들어, 애플리케이션 상태관리에 MobX를 사용하고 있는 앱들을 Redux로 바꾸는 일)

나는 재사용을 달성하기 위한 HOC와 Functional Stateless Component에 중점을 둘 것이다. 당신은 ReactReact Native에 대해 익숙해야만 한다. Alexis Mangin이 그들의 차이점에 대해 설명한 좋은 을 썼다.

이 글에는 많은 양의 세부 사항이 있다. 나는 컴포넌트 리팩토링에 대해 점차적인 접근 단계로 설명할 것이다. 만약 이러한(HOC같은) 아이디어에 친숙하다면, 시간을 절약하기 위해서, 혹은 인내하기 힘들다면, The Payoff: Reusing the Components(최종 Github 저장소)로 점프하라. 재사용된 컴포넌트들을 이용해서 추가적인 애플리케이션을 만드는 것이 얼마나 쉬운 일인지 알게될 것이다.

Higher Order Components와 Stateless Functional Components란 무엇인가?

React 0.14에서 Stateless Functional Components가 도입되었다. 바로 컴포넌트를 렌더링하는 함수들이다. 문법이 더 간단하다. 클래스 정의나 컨스트럭터는 존재하지 않는다. 그 이름이 뜻하는 바와 같이 상태 관리도 없다(setState를 쓰지 않는다). 조금 뒤에서 튜터리얼의 후반부에 예제를 가지고 더 이야기하겠다.

Cory House가 좋은 소개글을 썼다.

Higher Order Components(HOC)는 새로운 컴포넌트를 만들어내는 함수다. 다른 컴포넌트(혹은 컴포넌트들)를 감싸고, 감싸진 컴포넌트를 캡슐화한다. 예를들면, 간단한 텍스트 박스를 상상해보자. 여기에 자동완성 기능을 추가하고 싶다. 그렇다면 HOC를 만들어서 이를 통해 텍스트박스를 감싸면 자동완성 텍스트 박스를 사용할 수 있다.

const AutocompleteTextBox = makeAutocomplete(TextBox);
export AutocompleteTextBox;

//…later

import {AutoCompleteTextBox} from ./somefile;

페이스북의 문서가 여기 있다. franleplat가 또한 상세한 내용의 을 썼다.

우리는 앞으로 HOC와 Stateless Function Components를 몇몇 지점에서 사용할 것이다.

샘플 애플리케이션

우리는 매우 간단한 애플리케이션으로 시작할 것이다. 단순한 서치 박스다. 쿼리를 입력하면 결과 목록을 얻을 수 있다. 우리 경우에는 이름으로 색상을 찾을 것이다.

화면이 하나뿐안 애플리케이션이다. 컴포넌트 재사용에 집중하기 위해 라우트나 여러 화면으로 구성된 애플리케이션을 사용하지는 않을 것이다.

두 번째로, 우리는 애플리케이션 한 쌍을 추가할 것이다(React와 React Native). 컴포넌트를 추출해서 재사용할 애플리케이션들이다.

Github 저장소 브랜치에 시작점의 애플리케이션이 있다(마지막 결과는 여기다). React(web), React Native(mobile)에 대한 전체 세부 사항을 README에 적어두었다. 그러나 여기에서 개요를 설명한다:

  • create-react-app을 통해 React 애플리케이션을 시작한다
  • React/web 애플리케이션에는 Material UI를 사용한다
  • react-native init을 통해 React Native 애플리케이션을 시작한다
  • 앱 상태 관리는 MobX를 이용한다. (Michel Weststrate, Mobx의 창시자가 훌륭한 튜토리얼을 이곳이곳에 두었다)

https://colors-search-box.firebaseapp.com/ 에서 웹 버전의 동작하는 데모를 확인할 수 있다. 각각의(web, 그리고 mobile) 스크린샷은 아래와 같다.

재사용을 위한 리팩토링

코드 재사용은 관점에 대한 것이다

코드 재사용의 기본은 간단한다. 메서드(혹은 클래스나 컴포넌트)를 코드베이스에서 추출한 다음, 포함된 값들을 파라미터로 바꾼다. 그리고 그것들을 다른 코드베이스에서 사용한다. 그러나 재사용된 요소의 이점은 썩 많지 않을 때가 많으며 공유된 코드를 유지하는데 많은 비용을 소모해야할 수도 있다.

그러나 나는 다음 지침들을 적용함으로써 지속적인 재사용을 달성했다. 관심사 분리, 단일 책임 원칙, 그리고 복사본 제거.

Separation of Concerns(SoC)와 Single Responsibility Principle(SRP)는 동전의 양면이다. 주요 아이디어는 주어진 코드는 반드시 하나의 주요 목적을 가져야 한다는 것이다. 하나의 목적만 있다면, Separation of Concerns는 제품에 자연스럽게 반영된다. 하나의 목적을 가진 요소는 아마도 두 가지 책임을 가진 영역으로 섞이지 않을 것이다.

많은 IDE와 개발 도구들은 복사된 코드들을 병합하는 자동화 기능을 제공한다. 그러나 유사한 디자인 사이에서 복사본 제거는 더 어렵다. 당신은 코드 블록을 재정렬해야하는 복사본들을 직접 “봐야”만 한다.

이 아이디어를 적용하는 것은 퍼즐 조각을 움직여서 조각들이 만나는 곳, 조각들이 드러내는 패턴이 무엇인지 찾는 것과 동일하다.

복사본을 찾으러 떠나보자.

복사본 보기

웹과 모바일 애플리케이션은 두 메인 컴포넌트가 있다. 웹 애플리케이션에서는 App.js

모바일 애플리케이션에서는 SearchView.js

구조 개요는 다음과 같다.

거의 동일하지만 React와 React Native간 플랫폼 차이점이 있다

두 컴포넌트는 유사한 구조를 지녔다. 이상적으로는 다음과 같이 생긴 컴포넌트를 공유할 수 있다.

우리의 목표: 공통의 공유된 컴포넌트 세트

유사-코드로는 다음과 같다.

유감스럽게도 두 애플리케이션 사이에는 매우 적은 코드만이 공통적이다. React가 사용하는 컴포넌트(이 경우에는 Material UI)는 React Native가 사용하는 컴포넌트들과 다르다. 그러나 관심사 분리를 통해 개념적인 중복을 제거할 수 있다. 그리고 컴포넌트를 단일 책임을 지도록 리팩토링한다.

관심사 분리와 단일 책임

App.jsSearchView.js는 모두 도메인 로직(우리의 애플리케이션 로직)과 플랫폼 구현, 라이브러리 통합이 섞여 있다. 이들을 고립시켜서 디자인을 향상시킬 수 있다.

  • UI 구현: ListItem, ListView를 분리하는 것 등
  • 상태 변경에 대한 UX: 결과를 보여주거나 업데이트하는 것으로부터 submitting을 분리 등
  • 컴포넌트들: search input, search results (list) 그리고 각각의 search result(list item) 등은 각각 컴포넌트로 분리해야만 한다

마지막으로, 이러한 많은 변화들이 아무 것도 망치지 않는 것을 보장하기 위한 자동화된 테스트를 통해 리팩토링이 완료된다. 이러한 간단한 “smoke” 테스트를 추가할 것이다. 이 Github 저장소/태그 에서 확인할 수 있다.

Stateless Function Components 추출

쉽고 분명한 것부터 리팩토링하자. React는 컴포넌트에 대한 것이므로 컴포넌트들을 분리하자. 읽기 쉬운 Stateless Functional Components를 사용할 것이다.

SearchInput.js를 다음과 같이 생성할 수 있다:

React의 정수는 UI/View 프레임워크고, 위 컴포넌트에서 그 정수를 볼 수 있다.

오직 2개의 임포트된 엘레멘트만 있다: React (JSX를 위한 요구사항) 그리고 Material UI의 TextField - MobX도 없고 MuiThemeProvider도 없다. 색상 등등도 없다.

이벤트 핸들링은 핸들러(파라미터로 주어진)에게 위임된다. 단, Enter 키를 누르는 것을 제외되었다. 이것은 input box의 구현 고려사항이고, 이 컴포넌트에 캡슐화되야만 한다. (예를 들어, 다른 UI 위젯 라이브러리는 enter 키를 누르면 서브밋하는 기능이 포함되어 있을 수도 있다)

리팩토링을 이어가자. SearchResults.js를 생성할 수 있다:

SearchInput.js와 유사하다. 이 Stateless Functional Components는 단순하고 2개의 임포트만 가지고 있다. 관심사 분리(그리고 SRP)에 따라, 이 컴포넌트는 ListItem이라는 개별 검색 결과를 위한 파라미터를 전달받는다.

ListItem을 감싸는 Higher Order Component를 생성할 수도 있다. 그러나 현재는 Stateless Functional Components를 사용할 것이다. 나중에 HOC를 사용할 것이다. (점차적으로, 우리는 SearchResults.js를 HOC로 리팩토링할 것이다.)

개별 검색 결과를 위해서, ColorListItem.js를 만들 것이다:

이제 App.js를 리팩토링할 필요가 있다.

Higher Order Components 추출

가독성을 위해서 App.jsSearchBox.js로 이름을 변경하겠다. 이 컴포넌트의 리팩토링에는 몇 가지 선택지가 있다.

  1. SearchBoxColorListItemSearchResults로 전달하게 하기(prop으로)
  2. index.jsColorListItemSearchBox에 전달하고, SearchResults로 전달하게 하기
  3. SearchBox를 Higher Order Component(HOC)로 변환하기

(1) 방법은 다음과 같다:

아무것도 잘못된 것이 없다. SearchInput.jsSearchResults.js를 추출하는 논리적인 결론이다. 그러나 SearchBoxColorListItem이 바인딩되어서 관심사 분리를 위반한다. (SearchResults의 재사용도 제한한다.)

(2) 관심사들을 분리해서 고쳐보자

(재사용성을 명확히 하려고 colors prop을 searchStore로 이름을 변경했다.)

그러나 사용처를 보면 ColorListItemindex.js에서 prop으로 전달하고 있음을 알 수 있다.

다음 코드와 비교해보자:

(3)의 경우, 즉 HOC를 사용했을 때의 index.js다. 사소한 차이지만 중요하다. ColorSearchBoxColorListItem을 포함하고 있으며, ColorSearchBox은 자신이 사용하고 있는 특정 search result 컴포넌트를 캡슐화한다.

(searchStore, Colors는 prop이다. 애플리케이션 내에서 하나의 인스턴스여야만 한다. 하지만 주어진 구성 요소의 인스턴스, 즉 ColorSearchBox가 여러 개 있을 수 있다.)

따라서, SearchBox.js를 HOC로 다음과 같이 만들 수 있다.

SearchBox.js가 이전 섹션(복사본 보기)의 유사코드와 닮아보인다는 것을 알아차릴 수 있다. 잠시 후에 더 정제해볼 것이다.

React Native 컴포넌트 리팩토링

모바일 애플리케이션과 추출된 컴포넌트들을 이전 패턴에 따라 다음과 같이 리팩토링할 수 있다. SearchInput을 추출하는 것과 같은 모든 세부사항을 살펴보지는 않을 것이다. 그러나 이 사항들은 READMEGithub 저장소 브랜치에 있다.

대신, 공통의 SearchBox를 리팩토링하는데 집중할 것이다. 이 컴포넌트는 web(React)와 mobile(React Native)에서 모두 사용할 것이다.

Web과 Mobile 양쪽에서 공유하는 컴포넌트 추출하기

명확히 하기 위해서 SearchInput.js, SearchResults.js, SearchBox.jsWebSearchInput.js, WebSearchResult.js, WebSearchBox.js로 개명했다.

(Web)SearchBox.js를 보자

2-10, 19, 20, 26, 27 번째 줄은 React에 특정된 것이다.

MuiThemeProvider는 Material UI components의 container고, 오직 Material UI에만 직접적인 의존성이 있다. 그러나 SearchInputSearchResult에도 묵시적인 의존성이 있다. 이러한 의존성을 SearchFrame 컴포넌트를 도입해서 분리시킬 수 있다. 이 컴포넌트는 MuiThemeProviderSearchInputSearchResults을 하위 컴포넌트로 갖고 캡슐화시킬 것이다. 그 다음엔 SearchBox HOC를 만들 수 있다. SearchBoxSearchFrame, SearchResults, SearchInput을 사용할 것이다.

새로운 SearchBox.js는 다음과 같다.

복사본 보기 섹션의 유사코드와 비슷해보인다.

WebSearchBox.js의 내용을 바꿀 차례다

WebSearchBox (26번째 줄) 은 SearchBox HOC를 사용한 결과다.

children은 특별한 React prop이다. 이 경우에는 WebSearchFrameWebSearchInputWebSearchResults을 포함하고 렌더링하도록 해준다. SearchBox에 의해 제공된 파라미터로 말이다. children prop에 대해 더 알고 싶다면 이곳을 확인하라.

또한 WebSearchResults를 HOC로 변경할 것이다. ListItem을 HOC 조합의 한 부분으로 캡슐화해야만 한다.

이제 재사용가능한 컴포넌트 세트를 가지게 되었다. (여기 Github 저장소와 브랜치가 있다. 주의, 몇몇 디렉토리는 명확성을 위해 이름을 바꿨다.)

결과: 컴포넌트 재사용

우리는 Github 저장소 검색 앱을 만들었다. (Github는 API key 없이 API를 사용하는 것을 허용한다. 이 튜토리얼에서 편리하게 쓰였듯이 말이다.)

초기설정과 같은 세부사항은 건너뛸 것이지만, 요약하자면 다음과 같다.

  • 웹 앱을 위해서는 create-react-app을 사용한다. 모바일 앱을 위해서는 react-native init을 사용한다
  • MobX, Material UI(웹앱용), qs(쿼리 스트링 인코딩) 등등을 추가한다. 더 자세한 내용은 package.json에 나와있다(, 모바일)

노력의 대부분은 새로운 검색 스토어를 작성하는 일이다. 이를 통해 컬러 대신에 Github 저장소들을 Git API를 통해 검색한다. 다음과 같이 github.js를 생성할 수 있다.

(유닛 테스트는 이곳에 있다)

단순함을 위해 몇몇 공통 파일을 복사할 것이다. GitHub 저장소에서는 파일을 복사할 때 약간의 편리함을 위해 webpack을 사용한다. 자바스크립트 프로젝트에서 파일/모듈을 공유하는 것은 보통 NPM이나 Bower를 이용한다. (돈을 지불하면 private module을 등록할 수 있다) 혹은 Git submodules를 사용할 수도 있다. 비록 어설프더라도 말이다. 우리는 모듈 배포가 아니라 컴포넌트 재사용에 집중하고 있기 때문에 그저 파일을 복사하는 조금 우아하지 못한 짓을 하는 것이다.

나머지는 쉽다. app.js를 삭제하고(App.test.js도) index.js의 내용을 다음과 같이 바꾼다.

이제 npm start를 실행하면 다음 화면을 볼 수 있다.

(https://github-repo-search-box.firebaseapp.com 에 가서 라이브 버전을 볼 수 있다)

React Native:Github 모바일 앱

github.jsMobileSearch*.js를 복사한 다음, GitHubMobileSearchBox.js를 생성한다.

그리고 index.ios.js의 내용을 다음과 같이 변경한다.

두 개의 파일만으로 새로운 모바일 앱이 만들어진다. react-native run-ios

리팩토링은 어려운 작업일 것이지만, 컴포넌트 재사용은 새로운 두 가지의 앱을 간단히 만들어낼 수 있다.

리뷰와 요약

우리 컴포넌트들에 대한 다이어그램을 살펴보자:

리팩토링 결과는 훌륭하다. 새로운 앱에서는 특정 도메인 로직에 집중할 수 있게 해준다. 단지 GitHub API 클라이언트와 저장소 결과를 렌더링하는 법만 정의했을 뿐이다. 나머지는 “무료”로 제공된 것이다.

게다가, 비동기 문제를 다룰 필요가 없다. 예를 들자면 github.js에서의 비동기 fetch 호출에 대해서는 알지도 못한다. 이는 리팩토링 방식의 놀라운 이점 중 하나며 Stateless Functional Components를 활용한 방법이다. 프라미스와 비동기 프로그래밍은 오직 필요한 곳, github.js에서만 발생할 뿐이다.

이러한 기술들을 몇 번 정도 적용해보면 컴포넌트를 추출하고 재사용하는 것이 더 쉬워질 것이다. 아마도 코딩이 패턴화되면 새로운 뷰의 시작지점에서부터 재사용 가능한 컴포넌트를 작성하게될지도 모른다.

또한 recompose와 같은 라이브러리를 살펴보면 HOC를 더 쉽게 작성할 수도 있다.

최종 GitHub 저장소를 살펴보고 당신만의 재사용가능한 컴포넌트에 대한 리팩토링 방법을 알려주세요.

다음: Microinterations and Animations in React. 이 게시물에 ♡를 눌러주시고 Medium혹은 twitter에서 저를 팔로잉 해주시기 바랍니다.

원문보기

새로운 언어의 문법들부터 JSX같은 커스텀 파싱까지, 자바스크립트 작성을 엄청나게 동적으로 만들어준 ES6와 바벨같은 것들에 감사한다. 나는 스프레드 오퍼레이터에 팬이 되었다. 이 3개의 점은 아마도 당신의 자바스크립트 태스크를 완료하는 방법을 바꿔놓을 것이다. 이 글의 다음 부분에서 내가 최고로 좋아하는 스프레드 오퍼레이터의 사용법을 나열할 것이다!

Apply없이 함수 호출하기

Function.prototype.apply를 호출할 때, 인자를 배열로 전달하기 때문에 인자들을 배열에 세팅해야 한다.

function doStuff (x, y, z) { }
var args = [0, 1, 2];

// Call the function, passing args
doStuff.apply(null, args);

스프레드 오퍼레이터를 쓰면 전부 함쳐서 apply를 쓰는 것을 피할 수 있다. 단순히 함수 호출 시에 배열 앞에 스프레드 오퍼레이터를 붙이기만 하면 된다.

doStuff(...args);

코드는 짧아지고, 말끔해지고, 쓸모없는 null을 넣어야할 필요도 없다! The code is shorter, cleaner, and no need to use a useless null!

배열 합치기

항상 배열을 합치는 다양한 방법들이 있었다. 그러나 스프레드 오퍼레이터는 배열을 합치는 새로운 방법을 제공한다.

arr1.push(...arr2) // arr2의 항목들을 뒤쪽에 추가한다
arr1.unshift(...arr2) // arr2의 항목들을 앞쪽에 추가한다

만약 두 배열을 함칠 때 아무 곳에서 요소를 추가하고 싶다면, 다음과 같이 하면 된다:

var arr1 = ['two', 'three'];
var arr2 = ['one', ...arr1, 'four', 'five'];

// ["one", "two", "three", "four", "five"]

다른 메서드들 보다 더 짧은 문법에 위치 제어 기능까지 추가된다!

배열 복사하기

배열을 복사하는 것은 빈번한 작업이다. 이전에는 Array.prototype.slice를 사용하곤 했다. 그러나 새로운 스프레드 오퍼레이트를 쓰면 배열을 다음과 같이 복사할 수 있다.

var arr = [1,2,3];
var arr2 = [...arr]; // like arr.slice()
arr2.push(4)

기억하라: 배열 안의 오브젝트는 여전히 레퍼런스다. 따라서 모든 것의 자체가 “카피”되는 것은 아니다.

arguments나 NodeList를 배열로 변환하기

배열을 복사하는 것과 비슷하게 Array.prototype.slice를 이용해서 NodeList와 arguments 객체들을 진짜 배열로 변환했었다. 그러나 이제 스프레드 오퍼레이터로 작업을 완료할 수 있다.

[...document.querySelectorAll('div')]

심지어 arguments를 함수 시그니처안에서 배열로 변환할 수도 있다:

var myFn = function(...args) {
// ...
}

Array.from을 사용할 수도 있다는 것을 잊지 말자!

Math 함수 사용하기

당연히 스프레드 오퍼레이터가 배열을 다른 arguments로 “펼치”기도 한다. 따라서 어떤 함수든 스프레드를 써서 arguments 수가 몇 개라도 받을 수 있는 함수들에 arguments를 전달할 수 있다.

let numbers = [9, 4, 7, 1];
Math.min(...numbers); // 1

Math 오브젝트의 함수 세트들은 스프레드 오퍼레이터를 함수 인자로 사용하는 것에 대한 완벽한 예제다.

재미있는 디스트럭쳐링

디스트럭쳐링은 나의 많은 React 프로젝트에서 사용한 재미있는 연습이다. 뿐만아니라 Node.js 앱에서도 마찬가지다. 레스트 오퍼레이터과 디스트럭처링을 통해 정보를 추출해서 변수에 할당할 수 있다. 다음과 같이 말이다:

let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
console.log(x); // 1
console.log(y); // 2
console.log(z); // { a: 3, b: 4 }

나머지 프로퍼티들은 스프레드 오퍼레이터 뒤쪽의 변수에 할당된다!

ES6은 자바스크립트를 효율적으로 만들 뿐만 아니라 재미있게도 만든다. 모던 브라우저는 모두 ES6 문법을 지원하기 때문에 별로 이들을 사용해서 놀아본 적이 없다면 반드시 해보는 것이 좋을 것이다. 환경에 관계없이 더 실험하는 것을 선호한다면 내 포스트 중 Getting Started with ES6를 확인해보라. 어떤 경우든 스프레드 오퍼레이터는 아주 유용한 피처고, 당신은 반드시 이에 대해 알아야 한다!

원문보기

Styled-Components는 React와 React Native를 위한 라이브러리로 자바스크립트와 CSS를 혼합하여 컴포넌트 레벨의 스타일링을 사용할 수 있게 해준다.

Styled-Components에 익숙하지 않다면 웹사이트를 방문해서 살펴보라.

기본적인 Styled-Components의 컴포넌트 모양은 다음과 같다:

지난 몇 주간 두 개의 프로젝트에서 Styled-Components를 사용했으며, 발견된 몇 가지 이점과 패턴을 적어보고자 한다.

압축된 스타일

Styled-Components는 스타일 값으로 함수를 전달할 수 있기 때문에, 기존 CSS처럼 HTML 엘리먼트에 새 클래스를 추가하는 것이 아니라 prop 값을 기반으로 해서 스타일을 바꿀 수 있다.

결과적으로 코드량이 줄어든다. 처음에 CSS를 Styled-Components로 전환했을 때, 아주 드라마틱한 향상을 볼 수 있었다.

원본 CSS

Styled-Components로 변환

명확해진 JSX

당신이 나처럼 JSX를 <div><span>으로 흐트려뜨리는 사람이라면, 아마도 Styled-Components가 기본적으로 더 의미에 맞는 컴포넌트 계층 구조를 구성하게 한다는 사실을 알 수 있을 것이다.

클래스를 통해 스타일링한 원본 JSX

className이 사용되지 않은 Styled-Components로 변환된 JSX! 태그의 의미를 보라

난 이미 당신의 JSX가 두 번째 예제처럼 보일 것이라는 것을 확신한다 :P. 그게 아니라면, Styled-Components는 성공으로 가는 기본 통로로 당신을 이끌어 당신에게 큰 도움을 줄 수 있을것이다.

스타일 합성

이것은 Styled-Components에서 내가 가장 좋아하는 피처다. 한번 Styled-Components를 생성하고 나면 새로운 Styled-Components에 쉽게 합성할 수 있다.

왜냐면 Styled-Components에게 DOM 엘레멘트 뿐만 아니라 컴포넌트도 전달할 수 있기 때문이다.

Message를 가지고 합성된 두 새로운 컴포넌트, Success와 Danger

Prop 필터링

React 15.2.0부터 DOM 엘레멘트가 알지 못하는 prop을 전달하면 경고가 발생한다. <div foo="foo">와 같이 전달하면 “foo”가 알지못하는 속성이라는 경고 메시지가 나온다.

때로는 컴포넌트가 가능한 모든 DOM 속성을 허용하고 내부의 DOM 엘레멘트로 이를 전달해야하는 경우가 있다. 모든 DOM 속성을 수동으로 지정하여 이 작업을 수행하기는 어렵다. 따라서 이러한 컴포넌트는 props를 다음과 같이 펼쳐버린다: <div {...props}>

우리는 앞서 언급한 알지못하는 prop 경고를 피하기 위해 유효한 DOM 속성이 아닌 prop을 필터링하기 시작했다. 흥미롭게도 Styled-Components는 이미 내부적으로 이 작업을 수행하고 있는 잘 관리되는 라이브러리라서 이러한 필터링에 대한 항목을 업데이트할 필요가 없었다.

<span>과 같은 DOM 엘레멘트에 유효한 DOM 속성을 필터링하는 함수를 가지고 있었다

이런 필터링으로부터 자유로워졌다! 심지어 아무런 스타일도 필요하지 않은 경우에도!

이들은 우리가 경험한 패턴이나 이점의 일부에 지나지 않는다. 우리가 계속 사용한다면 시간이 갈 수록 더 많은 것들이 나타나리라 확신한다.

Styled-Components를 만든 Max Stoiber와 Glen Maddern에게 큰 감사를 표한다.

원문보기

redux를 사용해봤다면 HTTP 리퀘스트나 타이머 같은 비동기 조작을 사용하여 상태를 변경할 때 문제를 겪어봤을 것이다.

redux에 의해 제공되는 제약에 묶여있지 않다면, 우리는 Promise resolve나, 혹은 setTimeout 안의 mutate하는 함수를 통해 애플리케이션과 상태를 흩뜨릴 것이다. 하지만 우리는 mutable 상태와, 일관성없음과 버그들로 인해 불타버릴 것이라는 것을 더 잘 알고 있다.

우리 논의를 일반적으로 두기 위해서, 이미 상태를 반영하는 리듀서들을 만들어두었다고 가정하자. 이 방법으로, 액션과 미들웨어에 대해서 할 수 있을 것이다.

redux를 사용하여 서버에 비동기 요청을 수행하기 위한 여러가지 액션들을 정의한다:

  1. MAKE_ASYNC_REQUEST
  2. MAKE_ASYNC_REQUEST_PROGRESS
  3. MAKE_ASYNC_REQUEST_SUCCESS
  4. MAKE_ASYNC_REQUEST_FAILURE

비동기 작업을 시작해야할 필요가 있을 때, 즉 사용자 액션이나 애플리케이션 시작 시점에서, 첫 번째 액션이 디스패치된다. 두 번째 액션은 선택사항이며, 요청의 진행 상황(로딩 바를 말함)을 나타내기 위해 디스패치된다. 이런 요구사항이 없을 경우, 로딩 인디케이터를 화면에 나타내는 플래그를 애플리케이션 상태에 두는 것이 최고다.

세 번째 및 네 번째 액션 중 하나만 디스패치된다. 각각 성공 또는 실패를 나타내며, 그에 따른 애플리케이션 상태 변경과 다시 그에 따라 UI에 반영된다.

인기있는 추상화는 액션 대신 함수를 디스패치하는 것이다. 이 함수는 첫 번째 인수로 디스패치에 대한 엑세스 권한을 가지며 필요에 따라 여러 액션을 디스패치할 수도 있다.

thunk를 이용한 비동기 동작 수행

우리는 redux-thunk라는 미들웨어를 redux에 사용하여 함수 디스패치를 다룬다. 여기서 주목해야할 한 가지는 flux 표준 액션으로 변환할 수 있는 적절한 미들웨어가 있다면, redux를 사용해서 모든 것을 디스패치할 수 있다는 것이다.

다룰 수 있는 적절한 redux 미들웨어가 있다면 Promises, Observables 혹은 심지어 Generators라도 디스패치할 수 있다. 비동기 작업을 기술할 때 thunks나 promises를 사용하는 것은 파악하기 어려울 수 있다.

또한, 그들 자신의 각 효과에 대해 파악하는 것이 제한된다. 어떤 경우에는 오프라인 store나, 웹 API, 혹은 기존 앱 상태의 조각에 대해 입력해야할 필요가 있을 수 있다.

간단히 말해, thunks는 좋은 시작점은 될 수 있겠지만, 확장에 쓰이기는 어렵다는 것이다. 더 적합한 접근 방법은 redux-saga 미들웨어를 사용해서 비동기 작업에 대한 워크플로우를 오케스트레이션하는 것이다.

saga는 한 프로세스를 몇 개의 더 작은 프로세스로 나눌 수 있도록 하는 오류 관리 패턴이다. 하위 프로세스 중 하나가 성공하거나 실패하면, 그 정보로 애플리케이션 상태를 업데이트한다.

sagas를 더 잘 이해하려면 다음 비디오를 참고하라:

redux-saga는 또한 서로 통신하는 여러 프로세스에 의존하는 동시성 패턴인 Communicating Sequential Processes(CSP)에 의해 영감을 얻었다. 우리 예제의 경우, 주어진 순간에, 오직 두 개의 통신하는 프로세스만이 존재한다. 모든 프로세스들은 미들웨어와 직접 통신한다.

프로세스는 제네레이터로 표현된다. 제네레이터는 잠시 실행을 멈췄다가 다시 멈춘 부분부터 시작할 수 있는 특별한 종류의 함수다. 제네레이터는 기본적으로 꼭두각시이며 이터레이터가 그것을 제어한다. 문자열의 경우에는, 이터레이터 자체의 next와 throw 메서드에 의해 제어된다.

제네레이터는 단순하다. 이터레이터의 질문에 응답하고 이터레이터가 재응답하기 전까지 기다린다. 다음 질문에 도달하기 전까지 표현식들을 평가(evaluate)하고 그 값을 질문의 응답으로 돌려주는 것이다.

아래 예제에서, 제네레이터는 이터레이터에게 3을 알려주고 이터레이터는 에러를 제네레이터에게 던져서 응답한다. 또한 제네레이터 호출이 제네레이터 바디를 실행하지 않고 이터레이터만 생성하는 방법 대해 주목하라.

Kyle Simpson은 제네레이터에 대한 일련의 게시물을 작성했다. 제네레이터에 대해 더 알고싶다면 읽어보라:

그리고 당신이 물어보기 전에 내가 예제를 저곳으로부터 훔쳤다는 것을 알려준다. 미들웨어와 통신하기 위해 사가는 미들웨어에게 자신이 무엇을 하기를 원하는 지 알려준다. effect로 알려진 이것은 redux 액션과 평행성을 그린다(동일하다)

redux-saga는 몇 가지의 effects를 가지고 있다. 일부는 일반적이며, 일부는 redux에 특정된다. 그리고 다른 일부는 스레드와 같은 동시성을 가능케 한다. 다음은 몇 가지 인기있는 액션 생성자들이다:

effect explanation
put redux 스토어에 액션을 디스패치한다
select 셀렉터를 사용하여 기존 애플리케이션 상태의 일부를 얻어온다
call 다른 saga들이나 promise 등을 호출할 수 있다
take 액션이 디스패치되기를 기다린다
fork 서브 프로세스를 트리거한 뒤 완료를 기다리지 않고 이동한다
cancel 포크되었던 서브 프로세스를 취소한다
cancelled 현재 프로세스가 켄슬되었었는지 확인한다
delay 다음 구문으로 이동하기 전에 주어진 기간동안 대기한다, Promise를 리턴한다

saga를 가지고 동일한 비동기 요청을 만들어보자:

보라, 콜백이 없다

redux-saga로부터 임포트한 createSagaMiddleWare 함수를 호출해서 미들웨어를 구한다. 스토어를 만들 때 미들웨어를 등록하는 것 외에 추가적으로 애플리케이션의 메인 사가를 실행하는데도 쓰일 수 있다.

mainSaga를 사용해서 필요한 다른 모든 사가들을 시작할 수 있다. 이 사가들은 종종 워커 사가라고도 알려져있다. mainSaga는 sagaMiddleWare에 effect를 yield하여 info를 아규먼트로 사용해서 asyncRequestSaga를 call하라고 알려준다.

mainSaga 내에 여러 개의 사가가 있는 경우 call 대신 fork를 사용해야 한다. 왜냐하면 fork는 saga를 호출하고 다음 스템으로 이동하도록 디자인되어 있기 때문이다. call은 사가가 완료될 때까지 블록되기 때문이다.

asyncRequestSaga 내에서, 액션으로부터 몇몇 데이터를 받은 다음 call을 사용하여 사가 미들웨어에게 비동기 API를 호출하도록 알려준다. 약간의 복잡성을 추가기위해, 애플리케이션 상태가 필요하다고 가정하자. 우리는 사가 미들웨어에게 필요한 상태를 획득하는 함수를 알려줄 수 있다.

그 다음 put을 사용하여 디스패치해야하는 액션을 알려주자. 에러 핸들링 또한 쉽다. 그냥 try-catch를 사용하면 된다.

이 접근법을 좋아한다면, 아래의 몇몇 라이브러리들을 확인해야만 할 것이다. 경고하건대, 이 목록은 내 자바스크립트 윈도우 쇼퍼로서의 경험에 근거한 것이다.

만약 다른 언어들에도 관심이 있다면, 다음을 확인해봐도 된다:

이것들은 조금 부담 될 수도 있고 큰 영향을 주지 못할 수도 있지만 어쨌든 한번 확인해보라.