[리액트 주요 개념] 리액트로 생각하기

리액트

리액트의 가장 큰 매력 중 하나는 앱을 설계하는 방식이다. 이번에는 리액트로 상품들을 검색할 수 있는 데이터 테이블을 만드는 과정을 생각해보자.

목업으로 시작하기

JSON API와 목업을 디자이너로부터 받았다고 가정하자. 목업과 JSON은 아래와 같다. 목업

[
  {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"}
];

1단계 : UI를 컴포넌트 계층 구조로 나누기

첫 번째 할 것은 목업에서 모든 컴포넌트 주변에 박스를 그리고 이름을 붙이는 것이다. 어떤 것이 컴포넌트가 될 지는 우리가 함수나 객체를 만들 때 처럼 만들면 된다. 단일 책임 원칙을 가지고 하나의 컴포넌트가 하나의 일을 하는 것이 이상적이기 때문에 하나의 컴포넌트가 여러가지 일을 수행하거나 커질 경우에는 작은 컴포넌트로 분리되어야 한다. 각 컴포넌트가 JSON 데이터 모델의 한 조각을 나타내도록 분리해야 한다. 컴포넌트 위의 앱을 보면 다섯개의 컴포넌트로 이루어져 있고, 컴포넌트 별로 색이 다르다.

  1. FilterableProductTable(노란색) : 예시 전체를 포괄하는 컴포넌트이다.
  2. SearchBar(파란색) : 모든 유저의 입력(user input)을 받는다.
  3. ProductTable(연두색) : 유저의 입력(user input)을 기반으로 데이터 콜렉션을 필터링해서 보여준다.
  4. ProductCategoryRow(하늘색) : 각 카테고리의 헤더를 보여준다.
  5. ProductRow(빨간색) : 각각의 제품에 해당하는 행을 보여준다.

위의 컴포넌트를 계층 구조로 나열해보면 다음과 같다.

  • FilterableProductTable

    • SearchBar
    • ProductTable

      • ProductCategoryRow
      • ProductRow

2단계 : 리액트로 정적인 버전 만들기

컴포넌트의 계층 구조까지 만들고 나서는 앱을 실제로 구현해보면 된다. 가장 쉬운 것은 데이터 모델을 가지고 UI 렌더링은 되는데 아직 아무 동작이 없는 버전을 만드는 것이다. 정적 버전을 만드는 것은 생각은 적게 필요할 수 있지만 타이핑은 많이 필요하다.(하드코딩) 상호작용을 만드는 것은 타이핑은 적지만 생각은 많이 해야한다. 데이터 모델을 렌더링하는 앱의 정적버전을 만들 때는 다른 컴포넌트를 재사용 하는 컴포넌트를 만들고 props를 이용해서 데이터를 전달한다. 정적 버전을 만들때는 state를 사용하지 말아도 된다. state는 오직 상호작용으로 데이터가 바뀔 때 사용하는 것이다. 정적버전에서는 state가 필요 없다.

앱을 만들 때는 하향식(top-down) 또는 상향식(bottom-up)으로 만들 수 있는데, 간단한 예시에서는 보통은 하향식으로 만드는 것이 쉽고 일반적이나, 프로젝트 크기가 클 때는 상향식으로 만들고 테스트를 작성하면서 개발하는 것이 더 나을 수 있다. 이 단계가 마무리되면 재사용 가능한 컴포넌트드르이 라이브러리를 가지게 된다. 현재는 앱의 정적 버전이기 때문에 컴포넌트는 render() 메서드만 있지만 계층구조의 최상단 컴포넌트는 prop으로 데이터 모델을 받는다. 데이터 모델이 변경되면 ReactDOM.render()을 다시 호출하고 UI가 업데이트 된다. 리액트의 단방향 데이터 흐름은 모든 것을 모듈화 하고 빠르게 만들어 준다.

state와 props의 차이점 propsstate는 JS 객체이고 두 객체 모두 렌더링 결과물에 영향을 주는 정보를 가지고 있다는 공통점이 있다. props는 컴포넌트에 전달되는 반면에 state는 (함수 내 선언된 변수처럼) 컴포넌트 안에서 관리된다.

3단계 : UI state에 대한 최소한의 (하지만 완전한) 표현 찾아내기

UI에 상호작용을 만드려면 데이터를 변경할 수 있도록 state를 이용해야 한다. 하지만 앱을 잘 만들기 위해서는 변경가능한 state의 최소 집합을 생각해야 한다. 핵심은 중복 배제이다. 필요로 하는 최소한의 state를 찾아서 이것으로 모든 것들이 필요에 따라 그때 그때 계산되도록 해야한다. 만약 TODO 리스트를 만든다면, TODO 아이템을 저장하는 배열만 유지하고 아이템의 개수를 표현하는 state를 별도로 만들 필요는 없다. TODO 갯수를 렌더링한다면 TODO 아이템 배열의 길이를 가져오는 식으로 해야한다.

아까 전 예시 애플리케이션을 생각해보면 다음과 같은 데이터를 가지고 있다.

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

여기서 어떤 게 state가 되어야 하는지를 정해야 한다.

  1. 부모로부터 props를 통해 전달되는지? 만약에 전달되면 state가 아니다.
  2. 시간이 지나도 변하지 않는지? 변하지 않는다면 state가 아니다.
  3. 컴포넌트 안의 다른 state나 props를 가지고 계산 가능한가? 그렇다면 state가 아니다.

제품의 원본 목록은 props를 통해 전달되기 때문에 state가 아니다. 검색어나 체크박스는 state로 볼 수 있는데, 시간에 따라 변화하고 다른 걸로 계산될 수 없기 때문이다. 마지막으로 필터링 된 제품들의 목록은 원본 목록과 검색어, 체크박스 값으로 조합해서 계산할 수 있다. 결과적으로 유저가 입력한 검색어와 체크박스의 값이 state를 가지게 된다.

4단계 : State가 어디에 있어야 할 지 찾기

최소한으로 필요한 state를 찾았다면 어떤 컴포넌트가 해당 state를 소유할지를 정해야 한다. 이 부분을 처음에 가장 어려워한다. 아래 과정에 따라 결정해보자.

  1. state를 기반으로 렌더링하는 모든 컴포넌트를 찾자.
  2. 공통 소유 컴포넌트를 찾자. (계층 구조 내 특정 state가 있어야 하는 모든 컴포넌트들의 상위에 있는 하나의 컴포넌트)
  3. 공통 혹은 더 상위에 있는 컴포넌트가 state를 가져야 한다.
  4. state를 소유할 적절한 컴포넌트를 찾지 못하면, state를 소유하는 컴포넌트를 만들어서 공통 오너 컴포넌트의 상위 계층에 추가해야 한다.

위의 애플리케이션을 위 과정에 따라 적용해보자.

  • ProductTable은 state에 의존한 상품 리스트를 필터링해야 하고 SearchBar는 검색어와 체크박스의 상태를 표시해야 한다.
  • 공통 소유 컴포넌트는 FilterableProductTable이다.
  • 의미상으로 FilterableProductTable이 검색어와 체크박스의 체크 여부를 가지는 것이 타당하다. state를 FilterableProductTable에 두기로 했다. this.state = {filterText : '', inStockOnly: false}와 같이 state를 컴포넌트의 constructor에 추가하고, filterTextinStockOnly를 하위 컴포넌트들에 prop으로 전달하자.

5단계 : 역방향 데이터 흐름 추가하기

마지막으로는 역방향으로 데이터 흐름을 만드는 것이다. 계층 구조의 하단에 있는 폼 컴포넌트에서 FilterableProductTable의 state를 업데이트 할 수 있어야 한다. 리액트는 양방향 데이터 바인딩과 비교한다면 더 많은 코딩이 필요하지만 데이터 흐름이 명시적으로 보이게 되기 때문에 프로그램의 동작을 쉽게 파악할 수 있다. 사용자가 폼을 변경할 때 마다 사용자의 입력을 반영할 수 있도록 state가 업데이트 되어야 한다. 컴포넌트는 그 자신의 state만 변경할 수 있기 때문에 SearchBar에 콜백을 넘겨서 state가 업데이트 되어야 할 때마다 호출되도록 해야한다. input에 onChange 이벤트를 사용함으로써 변화에 대한 알림을 받을 수 있다. FilterableProductTable에서 전달된 콜백은 setState()를 호출하고 업데이트가 반영된다.

출처 : 리액트 주요 개념안내서


Written by@[Ykss]
고이게 두지 않고 흘려보내는 개발자가 되자.

GitHubInstagramLinkedIn