[React Docs 번역] Thinking in React

김승엽
Berkbach
11 min readApr 27, 2020

--

Photo by Charles Deluvio on Unsplash

🤔 React 처럼 생각하기

React는 JS로 크고 빠른 웹 애플리케이션을 만드는 최고의 방법이라고 생각합니다. 페이스북과 인스타그램으로 확장성을 입증되었습니다. React의 장점 중 하나는 앱을 설계하는 방법입니다. 이 문서는 React를 사용해서 검색 가능한 데이터 테이블을 만드는 과정을 알아보겠습니다.

목업(mock-up) 으로 시작하기 🚀

JSON API와 디자이너한테 받은 목업이 있다고 가정합시다.

JSON API로 받은 데이터는 이렇습니다.

1단계: UI를 컴포넌트 계층으로 쪼개기 🗂

첫 번째로 해야할 일은 각 컴포넌트 주위로 박스를 그리고 이름을 부여하는 것입니다. 만약 디자이너와 협업을 한다면 디자이너가 이미 그렸을 수 있습니다. 디자이너의 포토샵 레이어 이름이 컴포넌트의 이름이 될 수 있습니다!

하지만 무엇이 컴포넌트가 되어야 할지 어떻게 알 수 있을까요? 새로운 함수나 객체를 만들 때와 같은 기술을 사용하면 됩니다. 이러한 기술 중 하나는 단일 책임 원칙 입니다. 이것은 하나의 컴포넌트는 하나의 일만 수행해야한다는 원칙입니다. 만약 컴포넌트가 커진다면 작은 서브 컴포넌트로 분리해야합니다.

대부분 사용자에게 JSON 데이터 모델을 보여주기 때문에 모델이 잘 정의되어있다면 UI (컴포넌트 구조) 가 잘 연결될 것입니다. UI와 데이터 모델은 같은 정보 아키텍쳐(information architecture) 로 연결되는 경향이 있기 때문입니다. 각 컴포넌트가 데이터 모델의 하나의 조각과 연결되도록 UI를 나눠주세요.

앱에서 5개의 컴포넌트를 볼 수 있습니다. 각 컴포넌트에 들어가는 데이터를 이탤릭체 로 표기했습니다.

  1. FilterableProductTable (orange): 예제의 모든 컴포넌트를 포함합니다.
  2. SearchBar (blue): 모든 사용자 입력 을 받습니다.
  3. ProductTable (green): 사용자 입력 을 기반으로 하는 데이터 필터링해서 보여줍니다.
  4. ProductCategoryRow (turquoise): 각 카테고리 를 보여줍니다.
  5. ProductRow (red): 각 데이터의 제품 을 보여줍니다.

ProductTable 을 보면 테이블 헤더 (Name, Price) 가 있는데 이것은 컴포넌트가 아닙니다. 이것을 컴포넌트로 만들지는 선호도 문제입니다. 여기서는 이것을 ProductTable 의 일부로 남기겠습니다. 데이터 를 렌더링하는 것은 ProductTable 의 책임이기 때문입니다. 하지만 헤더가 복잡해지면 ProductTableHeader 로 나누는 것이 합리적일 수 있습니다.

이제 우리의 목업에서 컴포넌트를 확인했습니다. 이제 계층 구조로 정리하겠습니다. 다른 컴포넌트 안에서 나타나는 컴포넌트는 계층의 자식으로 나타내야합니다.

  • FilterableProductTable
    +
    SearchBar
    ProductTable
    — ㄴ
    ProductCategoryRow
    — ㄴ
    ProductRow

2단계: React로 정적 페이지 작성 🖋

계층구조로 된 컴포넌트를 만들었습니다. 이제 앱을 구현할 시간입니다. 가장 쉬운 방법은 데이터 모델을 가지고 있지만 양방향 통신이 안되는 UI를 렌더링하는 방식입니다. 정적인 버전은 많은 타이핑과 적은 생각이 필요하고, 양방향 통신을 추가하는 것은 많은 생각과 적은 타이핑이 필요하기 때문에 과정을 분리하는 좋은 방법입니다.

데이터 모델을 렌더링하는 앱의 정적인 버전을 작성하는 위해 다른 컴포넌트를 재사용하는 컴포넌트를 만들고 props 를 이용해서 데이터를 전달해야합니다. state 개념에 친숙하다고 해도 정적인 버전을 만드는 경우에는 절대로 state 를 사용하면 안됩니다. State는 오직 양방향 통신, 즉 시간에 따라 데이터가 변할 때를 위해 사용됩니다. 이 예제는 정적인 버전이기 때문에 필요없습니다.

앱을 만들 때 위에서 아래 또는 아래에서 위 로 작성할 수 있습니다. 이 말은 계층의 상단컴포넌트 (FilterableProductTable) 에서 시작하거나 하단컴포넌트 (ProductRow) 에서 시작하거나 상관 없다는 뜻입니다. 간단한 예제는 아래에서 위로 작성하는게 쉽지만 대게 큰 앱은 위에서 아래 로 작성하고, 테스트와 함께 작성합니다.

이 단계를 마치면 재사용이 가능하고 데이터 모델을 렌더링하는 컴포넌트 라이브러리를 가질 수 있습니다. 앱이 정적인 버전이기 때문에 컴포넌트는 render() 메소드만 가지게 됩니다. 계층의 최상단 컴포넌트 (FilterableProductTable) 은 데이터 모델을 props로 가지고 있습니다. 만약 근본적인 데이터 모델을 바꾼다면 ReactDOM.render() 를 다시 호출하고 UI 가 업데이트 됩니다. 그렇게 되면 UI 가 어떻게 변하고 어디서 변화가 생기는지 확인할 수 있습니다. React의 단방향 데이터 흐름 (또는 단방향 바인딩) 은 모든것은 모듈화하고 빠르게 만듭니다.

3단계: UI state 의 작지만 중요한 표현 찾기 🔍

양방향 통신이 가능한 UI를 만들기 위해서는 근본적인 데이터 모델을 바꿀 트리거가 필요합니다. React는 state 가 이것을 수행합니다.

정확하게 앱을 작성하려면 앱에 필요한 가장 작고 변화할 수 있는 state를 생각해야 합니다. 여기서 핵심은 중복배제 원칙 입니다. 애플리케이션이 필요로 하는 가장 최소한의 state를 찾고 이를 통해 나머지 모든 것들이 필요에 따라 그때그때 계산되도록 만드세요. TODO 리스트를 작성한다고 하면 TODO 아이템들을 배열로 유지해야합니다. 카운트가 가능한 state로 나누지 마세요. 대신 TODO 카운트를 렌더링하고 싶다면 TODO 아이템 배열의 길이를 구하면 됩니다.

예제 애플리케이션의 데이터 조각을 생각해보겠습니다.

  • 제품의 원본 리스트
  • 사용자가 전달한 검색어
  • 체크박스의 값
  • 필터링된 제품 리스트

하나씩 보면서 어떤게 state가 될지 찾아보겠습니다. 각 데이터에 3가지 질문을 하겠습니다.

  1. 이것이 props로 부모에게 전달받나요? 만약 그렇다면 state가 아닙니다.
  2. 이것이 시간이 지나도 변하지 않나요? 만약 그렇다면 state가 아닙니다.
  3. 컴포넌트에서 이것을 다른 state나 props를 통해서 계산할 수 있나요? 만약 그렇다면 state가 아닙니다.

제품의 원본 리스트는 props로 전달되므로 state가 아닙니다. 검색어와 체크박스의 값은 시간에 따라서 변하고 다른 것들로 부터 계산이 안되기 때문에 state로 보입니다. 마지막으로 필터링된 리스트는 제품의 원본 리스트와 검색어, 체크박스 값에 따라서 계산되기 때문에 state가 아닙니다.

4단계: State 위치 확인하기 📌

자 이제 앱의 state를 찾았습니다. 다음으로 어느 컴포넌트가 state를 변경하거나 소유할지 알아보겠습니다.

기억하세요: React는 모든 컴포넌트 계층이 단방향 데이터 통신입니다. 어떤 컴포넌트가 어떤 state를 가져야 할지 즉시 알기 쉽지 않습니다. 이것은 초심자가 이해하기 가장 어려운 부분입니다. 그러므로 아래 과정을 알아보겠습니다.

  • state를 기반으로 렌더링하는 모든 컴포넌트를 확인합니다.
  • 공통 소유 컴포넌트 를 찾습니다. (계층구조에서 특정 state가 있어야 하는 모든 컴포넌트의 상단 싱글 컴포넌트)
  • 공통 소유 컴포넌트 또는 다른 컴포넌트의 위에 있는 컴포넌트는 state를 가지고 있습니다.
  • state를 가질 적절한 컴포넌트를 못찾았다면 state를 가지고 있는 새로운 컴포넌트를 만들고 계층적으로 공통 소유 컴포넌트 상위 계층으로 추가합니다.

이 전략을 애플리케이션에 적용시켜보겠습니다.

  • ProductTable 은 state에 기반하는 필터링된 제품 리스트가 필요합니다. 그리고 SearchBar 는 화면에 보여줄 검색어와 체크 state가 필요합니다.
  • 공통 소유 컴포넌트FilterableProductTable 입니다.
  • 개념적으로 체크박스 값과 필터링 값은 FilterableProductTable 에 있어야 합니다.

좋습니다. state의 위치를 FilterableProductTable 안으로 결정했습니다. 첫 번째로 this.state = {filterText: ‘’, inStockOnly: false} 을 생성자 안에 작성합니다. 다음 filterTextisStockOnlyProductTableSearchBar 의 props 로 전달합니다. 마지막으로 이것들을 ProductTable 는 필터링으로 사용하고 SearchBar 는 폼의 필드 값으로 설정합니다. 이제 애플리케이션의 동작을 볼 수 있습니다. 코드에서 this.statefilterText"ball"로 설정하고 앱을 새로고침 해보세요. 데이터 테이블이 올바르게 업데이트 된 것을 볼 수 있습니다.

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

지금까지 우리는 계층 구조 아래로 흐르는 props와 state의 함수로써 앱을 작성했습니다. 이제 다른 방법으로 데이터 흐름을 만들 시간입니다. 계층구조의 깊은 곳에 있는 폼 컴포넌트는 FilterableProductTable 의 state를 업데이트해야합니다. React는 전통적인 양방향 데이터 바인딩과 비교하면 더 많은 타이핑을 필요로 하지만 데이터 흐름을 명시적으로 보이게 만들어서 프로그램이 어떻게 동작하는지 파악할 수 있게 도와줍니다.

지금 버전의 예제에서 입력이나 체크를 시도하면 입력값이 표시되지 않습니다. 이것은 의도된 것으로 input 의 값이 항상 FilterableProductTable 의 state와 같도록 설정했기 때문입니다.

무엇을 해야할지 생각해보겠습니다. 사용자가 폼을 바꿀 때 사용자 입력에 따라서 state가 업데이트 되기를 원합니다. 컴포넌트는 자신의 state만 업데이트하기 때문에 FilterableProductTableSearchBar 에게 state를 변경할 수 있도록 콜백함수를 넘겨줄 것 입니다.

“ 이게 전부입니다. ”

이것을 통해 React의 애플리케이션와 컴포넌트를 작성하는 방법을 배웠기를 바랍니다. 기존에적었던 코드보다 더 많은 타이핑을 요구할 수도 있지만 코드는 쓰는 일보다 읽는 일이 더 많다는 것을 기억하세요. 모듈화되고 명확한 코드는 읽기 더 쉽습니다. 큰 컴포넌트의 라이브러리를 시작한다면 명확성과 모듈화의 진가를 알 수 있습니다. 그리고 코드 재사용을 하면 코드가 줄어드는 것도 볼 수 있습니다.

📚 정리 및 참고 자료

  1. 목업은 “모형” 이란 뜻으로 최소한의 비용으로 사전에 검토하는 테스트 입니다.
  2. Mock-up
  3. 단일 책임 원칙

--

--