본문 바로가기
Front-end/ReactJS

React로 사고하기 !

by blogsy 2020. 12. 21.

react.js 의 파일구조를 어떻게 나눠야하는지 고민하다가 react.js 공식 문서에서 하나의 글을 발견했습니다. react로 프로젝트를 개발하며 많은 도움이 될 것 같아 한 번 정리해보려고 합니다.

 

ko.reactjs.org/docs/thinking-in-react.html

 

React로 사고하기 – React

A JavaScript library for building user interfaces

ko.reactjs.org


목업으로 시작하기

목업(Mock up)이란 프로토타입, 시제품, 견제품과 같은 말입니다. 말그대로 미리 그려보고 시작하라는 것입니다.

React 공식 문서의 예시를 가져와 보겠습니다 . JSON API와 목업을 디자이너로부터 받았다고 가정해 봅시다.

목업은 다음과 같을 것입니다.
JSON API는 위와 같은 데이터를 반환합니다.

밑에 사이트는 다음 프로젝트 시 제가 한번 사용해보고 싶어서 걸어두었습니다 ㅎㅎ;

ovenapp.io/

 

OvenApp.io

Oven(오븐)은 HTML5 기반의 무료 웹/앱 프로토타이핑 툴입니다. (카카오 제공)

ovenapp.io


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

한 가지만 기억하자 ! 

* 단일 책임 원칙 *

단일 책임 원칙(single responsibility principle)이란 모든 클래스는 하나의 책임만 가지며, 클래스는 그 책임을 완전히 캡슐화해야 함을 일컫는다. 라고 하네요.

 

각 컴포넌트가 데이터 모델의 한 조각을 나타내도록 분리해주세요.

  1. FilterableProductTable(노란색): 예시 전체를 포괄합니다.

  2. SearchBar(파란색): 모든 유저의 입력(user input) 을 받습니다.

  3. ProductTable(연두색): 유저의 입력(user input)을 기반으로 데이터 콜렉션(data collection)을 필터링 해서 보여줍니다.

  4. ProductCategoryRow(하늘색): 각 카테고리(category)의 헤더를 보여줍니다.

  5. ProductRow(빨강색): 각각의 제품(product)에 해당하는 행을 보여줍니다.

여기서 3번 연두색 영역을 보면, Name과 Price 만을 보여주는 헤더부분이 따로 컴포넌트로 분리되어있지 않습니다. 그러나 이 헤더가 기능이 많아지는 등 (정렬 등) 복잡해지면 ProductTableHeader 라고 따로 컴포넌트를 만드는게 적합하다고 합니다.

 

이제 이를 계층 구조로 나눠보면, 

 

 

  • FilterableProductTable

    • SearchBar
    • ProductTable

      • ProductCategoryRow
      • ProductRow

이렇게 됩니다.


2단계: React로 정적인 버전 만들기

컴포넌트 계층 구조를 만들었으니, 이제 앱을 실제로 구현해봅니다. 가장 쉬운 방법은 데이터 모델을 가지고 UI 렌더링은 되지만, 아무 동작도 없는 버전을 만드는 것입니다.

 

* 정적 버전 만들기 : 생각은 적게, 타이핑은 많이 필요

* 상호작용 만들기 : 생각은 많이, 타이핑은 적게 필요

 

그렇기 때문에 이렇게 과정을 나누는 게 좋다고 합니다.

 

데이터모델을 렌더링하는 앱의 정적 버전을 만들기 위해, 재사용 가능한 컴포넌트를 만들고 props를 이용하여 데이터를 전달해줍시다.

props는 부모->자식으로 데이터를 넘겨주기 위해 사용되는 방법입니다.

 

정적 버전을 만들 때는 state를 사용하지 말라고 하네요. 

(state의 역할 : 오직 상호작용을 위해, 즉 시간이 지남에 따라 데이터가 바뀌는 것에 사용)

 

(props와 state의 차이점을 모르겠다면 ? 밑에 사이트를 살펴보시기 바랍니다)

ko.reactjs.org/docs/faq-state.html#what-is-the-difference-between-state-and-props

 

컴포넌트 State – React

A JavaScript library for building user interfaces

ko.reactjs.org

 

앱을 만들 때 두 가지 방식이 있는데요, 하나는 하향식(top-down), 다른 하나는 상향식(bottom-up)입니다.

하향식이란, 계층 구조의 상층부에 있는 컴포넌트부터 만드는 것입니다. 위 예시로 보면 FilterableProductTable부터 하나씩 밑으로 내려가며 만드는 것이 되겠네요.

상향식이란, 반대로 계층 구조의 하층부에 있는 컴포넌트부터 개발하는 것입니다. 위 예시에서는 ProductRow 컴포넌트부터 올라가며 만드는 것입니다.

간단한 예시같은 경우는 하향식이 쉽지만, 프로젝트가 커지면 상향식으로 만들고 테스트를 작성하면서 개발하는 것이 더 쉽습니다.

 

이 단계가 끝나면 데이터 렌더링을 위해 만들어진 재사용 가능한 컴포넌트들의 라이브러리를 가지게 됩니다.

현재는 정적 버전이기 때문에 컴포넌트는 render() 메소드만 가지고 있습니다.

계층 구조의 최상단 컴포넌트(FilterableProductTable)는 prop으로 데이터 모델을 받고, 데이터 모델이 변경되면 ReactDom.render()을 다시 호출해서 UI가 업데이트됩니다.

(React의 단방향 데이터 흐름(one-way data flow)은 모든 것을 모듈화하고 빠르게 만들어 줍니다)


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

UI를 상호작용하게 만들려면 데이터 모델을 변경해야하는데, React에서는 State를 사용하여 변경합니다.

앱을 올바르게 만드려면 필요한 state의 최소 집합을 생각해야 합니다(중복 배제 원칙). 가장 최소한의 state를 찾고 이 state를 통해 나머지 필요한 것을 그때그때 계산되도록 만들어야 합니다.

 

만약 Todo 리스트를 만든다고 하면, Todo Item을 저장하는 배열만 유지하고, 아이템 개수를 따로 저장하는 state는 안만드는 겁니다. todoItem.length를 통해 구할 수 있을테니까요.

 

예시로 앱 내에 이러한 데이터들이 있다고 가정한다면, 어떤 것이 state가 될까요 ???

 

  • 제품의 원본 목록

  • 유저가 입력한 검색어

  • 체크박스의 값

  • 필터링 된 제품들의 목록

공식 문서에서 다음의 세 가지 질문을 통해 state를 결정할 수 있다고 합니다. 

 

  1. 부모로부터 props를 통해 전달됩니까? 그러면 확실히 state가 아닙니다.

  2. 시간이 지나도 변하지 않나요? 그러면 확실히 state가 아닙니다.

  3. 컴포넌트 안의 다른 state나 props를 가지고 계산 가능한가요? 그렇다면 state가 아닙니다.

더보기

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

결과적으로, 유저가 입력한 검색어, 체크박스의 값 만이 state가 되겠네요.

 


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

3단계를 통해 최소한의 state를 찾아냈는데, 어떤 컴포넌트에서 이 state를 가지고 있고, 변경해야 할까요?

React는 단방향 데이터 흐름을 따르기 때문에 바로 결정이 힘들 수도 있습니다. (저는 초보라 그런지 고민이 많이 되더라구요)

 

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

  • state를 기반으로 렌더링하는 모든 컴포넌트를 찾으세요.

  • 공통 소유 컴포넌트 (common owner component)를 찾으세요. (계층 구조 내에서 특정 state가 있어야 하는 모든 컴포넌트들의 상위에 있는 하나의 컴포넌트).

  • 공통 혹은 더 상위에 있는 컴포넌트가 state를 가져야 합니다.

  • state를 소유할 적절한 컴포넌트를 찾지 못하였다면, state를 소유하는 컴포넌트를 하나 만들어서 공통 오너 컴포넌트의 상위 계층에 추가하세요.

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

 

  • ProductTable은 state에 의존한 상품 리스트의 필터링해야 하고 SearchBar는 검색어와 체크박스의 상태를 표시해주어야 합니다.

  • 공통 소유 컴포넌트는 FilterableProductTable입니다.

  • 의미상으로도 FilterableProductTable이 검색어와 체크박스의 체크 여부를 가지는 것이 타당합니다.

더보기

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

 

예시를 보고 싶다면 =>  codepen.io/gaearon/pen/qPrNQZ

 

Thinking In React: Step 4

...

codepen.io


 

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

지금까지 아래로 흐르는 데이터 플로우를 이용했다면, 이제 반대를 만들어 보겠습니다.

계층 구조 하단에 있는 폼 컨포넌트에서 상단인 FilterableProductTable의 state를 업데이트 할 수 있어야 하니까요.

 

사용자가 폼을 변경할 때마다 사용자의 입력을 반영할 수 있도록 state를 업데이트 하겠습니다. 컴포넌트는 그 자신의 state만 변경할 수 있기 때문에 FilterableProductTable에서 SearchBar에 콜백을 넘기고, state가 업데이트 될 때마다 호출되도록 하면 됩니다.

input에 onChange 이벤트를 사용하여 state가 변경되는 것을 감지할 수 있습니다.


짧은 글이지만 하나하나 이해하려고 노력하면서 작성했더니 꽤 시간이 들었습니다. 이 글은 제가 이해하기 위해 작성한 글이기 때문에 타인이 보기엔 명확하지 않을 수 있고, 저의 이해가 잘못된 부분이 있을 가능성도 있기 때문에 꼭 공식문서를 확인하시기 바랍니다. 또한 공식문서에 유익한 정보가 많아요 ! 저도 하나하나 보고 있는 중입니다. ㅎㅎ 

 

앞으로 프로젝트에 이러한 react적 사고를 반영해봐야겠습니다 ! 

댓글