https://reactjs.org/docs/thinking-in-react.html 번역글

React는 자바스크립트로 크고 빠른 웹 애플리케이션을 만드는 최고의 방법이라고 생각합니다. Facebook과 Instagram도 React를 사용하였으며 앱이 커질 때 아주 유용했습니다.

React의 가장 중요한 부분 중 하나는 앱을 제작할 때 앱을 어떻게 만들지 생각하는 것입니다. 이 문서에서는 React를 사용하여 검색 기능이 포함된 제품 데이터 목록을 제작하는 과정을 안내합니다.


Start With A Mock(Mock으로 시작하기)

디자인과 JSON API의 모형 데이터를 갖고 있다고 가정하겠습니다. 디자인은 다음과 같습니다.

예시

JSON API는 다음과 같은 데이터를 반환합니다.

[
  {category: "Sporting Goods", price: "$49.99", stocked: true, name: "Football"},
  {category: "Sporting Goods", price: "$9.99", stocked: true, name: "Baseball"},
  {category: "Sporting Goods", price: "$29.99", stocked: false, name: "Basketball"},
  {category: "Electronics", price: "$99.99", stocked: true, name: "iPod Touch"},
  {category: "Electronics", price: "$399.99", stocked: false, name: "iPhone 5"},
  {category: "Electronics", price: "$199.99", stocked: true, name: "Nexus 7"}
];

Step 1: Break The UI Into A Component Hierarchy(1단계: UI를 컴포넌트 계층으로 분리하기)

가장 먼저해야 할 일은 모형의 모든 컴포넌트 주위에 박스를 그리고 모든 이름을 지정하는 것입니다. 디자이너와 함께 일하는 경우 이미 작업을 마친 상태 일 수 있습니다. Photoshop의 레이어 이름이 React 컴포넌트 이름이 될 수 있기 때문입니다.

그렇다면 어떤 것들을 컴포넌트로 만들어야 할까요? 새로운 함수나 객체를 만들것인지 결정이 필요합니다 여기에 핵심은 단일 책임원칙(single responsibility principle)입니다. 이는 하나의 컴포넌트가 한가지의 작업만 하는 것이 이상적이라는 것입니다. 컴포넌트가 점점 커진다면 서브 컴포넌트로 분리 되어야 합니다.

사용자에게 종종 JSON 데이터 모델을 표시하기 때문에 모델이 올바르게 구축 되었다면 UI와 컴포넌트 구조가 잘 맵핑 됩니다. 그 이유는 UI 및 데이터 모델이 동일한 information architecture 를 따르는 경향이 있기 때문입니다. 그러므로 UI를 컴포넌트로 분리하는 작업은 그리 어려운 일이 아닙니다. 컴포넌트를 하나의 데이터 모델을 나타내도록 분리하면됩니다.

컴포넌트 분리 예시

위 예시는 다섯 가지 컴포넌트가 존재합니다. 각 컴포넌트가 나타내는 데이터를 이탤릭체로 표시했습니다.

  • FilterableProductTable(오렌지색) : 예제 전체 포함
  • SearchBar(파란색) : user input(사용자 입력) 수신
  • ProductTable(녹색) : user input(사용자 입력) 을 기반으로 data collection(데이터 콜렉션) 을 표시하고 필터링합니다.
  • ProductCategoryRow(청록색) : 각 category(카테고리) 에 대한 제목을 표시합니다
  • ProductRow(빨간색) : 각 product(제품) 에 대한 행을 표시합니다 .

ProductTable을 보면 NamePrice 레이블을 포함한 테이블 헤더는 해당 컴포넌트에 존재하지 않습니다. 이것은 선호의 문제이며 어느쪽으로 할지 결정합니다. 이 예제에서는 ProductTableProductTable의 책임인 데이터 콜렉션 렌더링의 일부이기 때문에 ProductTable을 남겨두었습니다. 그러나 이 헤더가 복잡해지면 (즉 정렬을 위한 affordances 추가하는 등)이 자체의 ProductTableHeader 컴포넌트를 만드는 것이 더 합리적일 것입니다.

모형 컴포넌트를 확인 했으므로 이를 계층 구조로 정렬 해 봅시다. 이 작업은 쉽습니다. 모형의 컴포넌트에 나타나는 구성 요소는 자식으로 나타내면 됩니다.

  • FilterableProductTable
    • SearchBar
    • ProductTable
      • ProductCategoryRow
      • ProductRow

Step 2: Build A Static Version in React(2단계: React를 이용해 정적버전 만들기)

CodePen

이제 컴포넌트의 계층구조가 만들어졌으니 앱을 실제로 구현해볼 차례입니다.
가장 쉬운 방법은 데이터 모델을 가지고 UI를 렌더링은 하지만 아무 동작도 하지 않는 버전을 만들어보는 것입니다. 이런식으로 과정을 나누는 것이 좋습니다. 정적 버전을 만드는 것은 생각은 적게 필요하지만 타이핑이 많이 필요로 하고, 상호작용을 만드는 것은 생각은 많이 해야하지만 타이핑이 적게 필요로 하기 때문입니다.

데이터 모델을 렌더링하는 앱의 정적 버전을 제작하려면 다른 컴포넌트를 재사용하는 컴포넌트를 만들고 props를 사용하여 데이터를 전달합니다. props는 부모가 자식에게 데이터를 전달하는 방법입니다. 만약 state에 대해 알고 있다면 정적 버전을 만들기 위해 state를 절대 사용하지마세요. state는 오직 상호작용 위해, 즉 시간이 지남에 따라 데이터가 바뀌는 부분에 사용합니다. 앱의 정적 버전을 만들때는 필요하지 않습니다.

앱을 만들 때 top-down(하형식)이나 bottom-up(상향식)으로 만들 수 있습니다. 다시 말해 계층 구조의 상층부에 있는 컴포넌트(FilterableProductTable) 부터 만들거나 하층부에 있는 컴포넌트(ProductRow) 부터 만들 수도 있습니다. 간단한 예제에서는 보통 하향식으로 만드는 게 쉽지만 프로젝트가 커지면 상향식으로 만들고 테스트를 작성하면서 진행하는 것이 더 쉽습니다.

이 단계가 끝나면 데이터 렌더링을 위해 만들어진 재사용 가능한 컴포넌트가 완성됩니다. 현재는 앱의 정적 버전이기 때문에 컴포넌트는 render() 메서드만 가지고 있을 것입니다. 계층구조의 최상단 컴포넌트(FilterableProductTable)는 prop으로 데이터 모델을 받습니다. 데이터 모델을 변경하고 ReactDOM.render() 을 다시 호출하면 UI는 업데이트 됩니다. 어느 곳을 고쳐서 어떻게 UI가 업데이트되는 지 확인하는 일은 어렵지 않은데 지금은 크게 복잡한 부분이 없기 때문입니다. React의 one-way data flow(단방향 데이터 플로우) 또는 on-way binding(단방향 바인딩)는 앱을 모듈화 하기 좋고 빠르게 만들어줍니다.

이 단계를 실행하는 데 도움이 필요하다면 React 문서 를 참고하세요.

A Brief Interlude: Props vs State(짧은 소개: Props vs State)

React 에는 두가지 데이터 “모델”인 propsstate가 있습니다. 이 둘 사이의 차이점을 이해하는 것이 중요합니다. 만약 차이점이 제대로 기억나지 않는다면 공식 리액트 문서를 살펴보세요.


Step 3: Identify The Minimal (but complete) Representation Of UI State(3단계: UI state의 최소(하지만 완전한)사용 찾아내기)

UI를 상호작용하게 만드려면 기반 데이터 모델을 변경할 수 있는 방법이 있어야합니다. React는 state로 이를 쉽게합니다.

앱을 올바르게 구현하려면 앱에 필요한 최소한의 변경 가능한 state를 고려해야합니다. 여기서 핵심은 DRY(Don't Repeat Yourself)입니다. 애플리케이션이 필요로하는 state를 절대적으로 최소한으로 표현하고 다른 것들은 필요할때 state로 계산합니다. 예를 들어 TODO 목록을 작성하는 경우 TODO항목을 위한 배열만 유지하고 배열 길이에 대한 state를 갖을 필요가 없습니다. 대신 TODO 개수 렌더링이 필요하다면 TODO items 배열의 길이를 사용하면 됩니다.

예제 어플리케이션 내 데이터들을 생각해봅시다. 우리는 현재,

  • 제품의 원본 목록
  • 유저가 입력한 검색어
  • 체크박스의 값
  • 필터링된 제품 목록

각각 살펴보고 어떤것이 state가 되어야 하는지 살펴봅시다. 그 기준은 다음 세 가지로 구분할 수 있습니다.

  • props을 통해 부모로부터 전달됩니까? 그렇다면 state가 아닙니다.
  • 시간이 지나도 변함이 없습니까? 그렇다면 state가 아닙니다.
  • 컴포넌트의 다른 stateprops을 기반으로 계산할 수 있습니까? 그렇다면 state가 아닙니다.

제품의 원본 목록은 props으로 전달되므로 state가 아닙니다. 검색어와 체크박스는 state로 볼 수 있는데 시간이 지남에 따라 변하기도 하면서 다른 것들로부터 계산될 수 없기 때문입니다. 마지막으로 필터링된 목록은 state가 아닌데 제품의 원본 목록과 검색어, 체크박스의 값을 조합해서 계산해낼 수 있기 때문입니다.

결국 state

  • 유저가 입력한 검색어
  • 체크박스의 값

만 남습니다.


Step 4: Identify Where Your State Should Live(4단계: State가 어디에 있어야할 지 찾기)

CodePen

좋습니다. 이제 앱에서 최소한으로 필요하는 state가 뭔지 찾아냈습니다. 다음으로는 어떤 컴포넌트가 state를 변경하거나 소유 할 지 찾아야합니다.

기억하세요: React는 항당 컴포넌트 계층구조를 따라 아래로 내려가는 단방향 데이터 흐름을 따릅니다. 어떤 컴포넌트가 어떤 state를 가져야하는지 바로 결정하기 어려울 수 있습니다. 많은 초보자들이 이 부분을 가장 이해하기 어려워합니다 아래 과정을 따라해보세요.

어플리케이션이 가지는 각각의 state에 대해서

  • state를 기반으로 렌더링하는 모든 컴포넌트를 찾으세요
  • 공통 오너 컴포넌트 (common owner component)를 찾으세요 (계층 구조 내에서 특정 state를 필요로하는 모든 컴포넌트들의 위에 있는 하나의 컴포넌트).
  • 공통 오너 혹은 더 상위에 있는 컴포넌트가 state를 가져야합니다.
  • state를 소유할 적절한 컴포넌트를 찾지 못하였다면, 단순히 state를 소유하는 컴포넌트를 하나 만들어서 공통 오너 컴포넌트의 상위 계층에 추가하세요.

이 전략을 어플리케이션에 적용해봅시다.

  • ProductTablestate에 의존한 상품 리스트의 필터링해야하고 SearchBar는 검색어와 체크박스의 상태를 표시해주어야합니다.
  • 공통 오너 컴포넌트는 FilterableProductTable 입니다.
  • 의미상으로도 FilterableProductTable이 검색어와 체크박스의 체크 여부를 가지는 것이 타당합니다.

좋습니다. stateFilterableProductTable에 두기로 했습니다. 먼저 인스턴스 속성인 this.state = {filterText: '', inStockOnly: false}FilterableProductTableconstructor 에 추가하여 어플리케이션의 초기 상태를 반영합니다. 그리고 나서 filterTextinStockOnlyProductTableSearchBarprop으로 전달합니다. 마지막으로 이 props를 사용하여 ProductTable 의 행을 정렬하고 SearchBar 의 폼 필드 값을 설정하세요.

이제 어플리케이션의 동작을 볼 수 있습니다. filterTextball 로 설정하고 앱을 새로고침 해보세요. 데이터 테이블이 올바르게 업데이트 된 것을 볼 수 있습니다.


Step 5: Add Inverse Data Flow(5단계: 역방향 데이터 흐름 추가하기)

CodePen

지금까지 우리는 계층 구조 아래로 흐르는 propsstate의 함수로서 앱을 만들었습니다. 이제 다른 방향의 데이터 흐름을 만들어볼 시간입니다. 계층 구조의 깊숙한 곳에 있는 폼 컴포넌트에서 FilterableProductTablestate를 업데이트할 수 있어야합니다.

React는 이러한 데이터 흐름을 명시적으로 보이게 만들어서 프로그램이 어떻게 동작하는 지 쉽게 파악할 수 있게 하지만 전통적인 two-way data binding(양방향 데이터 바인딩)과 비교하면 더 많은 타이핑을 필요로 합니다.

현재 버전 예제에서 타이핑을 하거나 체크박스를 체크하려고 하면 React가 사용자의 입력을 무시합니다. 이는 의도한 것인데 FilterableProductTable 에서 넘어온 state가 항상 inputvalue prop과 동일하기 떄문입니다.

우리가 원하는 것을 생각해 봅시다. 사용자가 폼을 변경할 때마다 사용자 입력을 반영하도록 state를 업데이트합니다. 컴포넌트는 자신의 state만 업데이트할 수 있기 때문에 FilterableProductTableSearchBar에 콜백을 넘겨 state업데이트가 필요할때마다 호출 하도록 합니다. inputonChange이벤트를 이용하여 함수를 실행할 수 있습니다. FilterableProductTable에서 전달 받은 콜백은 setState()를 호출 할 것이고, 앱이 업데이트됩니다.

이 작업은 복잡해 보이지만 몇 줄의 코드밖에 안됩니다. 또한 앱의 데이터 흐름을 명시적으로 볼 수 있습니다.


And That’s It(이게 전부)

이 글을 통해 React를 이용하여 어플리케이션과 컴포넌트를 만드는데에 대한 사고방식을 얻어가길 바랍니다. 이전보다 많은 코드량이 필요할 수 있지만 코드를 쓸일보다 읽을 일이 더 많다는 사실을 기억해야 합니다. 모듈화 되고 명시적인 코드는 읽기 쉽습니다. 커다란 컴포넌트 라이브 러리를 빌드하기 시작할 때가 되면 이 명시성과 모듈성에 감사할 것이며, 코드 재사용으로 코드량이 줄어들기 시작할 것입니다.