Thinking in React

hyocho·2023년 4월 19일
0

React

목록 보기
10/23
post-thumbnail

리액트는 당신이 바라보는 디자인과 만드는 앱에 대한 시각을 바꿀 수 있다. 리액트로 사용자 인터페이스를 만들 때, 당신은 컴포넌트라고 불리는 조각으로 분할할 것이다. 또 각 컴포넌트들의 다른 시각적인 상태를 묘사할 것이고 마지막으로 컴포넌트들을 연결해서 데이터가 흐르게 만들 것이다.

Start with the mockup

JSON API와 mockup을 전달 받았다고 가정해보자.
JSON API는 아래와 같은 값을 반환한다.

[
  { category: "Fruits", price: "$1", stocked: true, name: "Apple" },
  { category: "Fruits", price: "$1", stocked: true, name: "Dragonfruit" },
  { category: "Fruits", price: "$2", stocked: false, name: "Passionfruit" },
  { category: "Vegetables", price: "$2", stocked: true, name: "Spinach" },
  { category: "Vegetables", price: "$4", stocked: false, name: "Pumpkin" },
  { category: "Vegetables", price: "$1", stocked: true, name: "Peas" }
]

mockup 은 아래와 같다


리액트에서 UI를 구현하려면 일반적으로 5단계를 따라야 한다.

Step 1: Break the UI into a component hierarchy

mockup 안의 모든 컴포넌트와 하위 컴포넌트에 박스를 그리고 이름을 정한다. 만약 디자이너와 협업한다면 그들이 이미 이름을 지어 놓았을지도 모른다.

배경에 따라 당신은 여러가지 방법으로 컴포넌트를 분할하는 방법을 생각할 수 있다.

  • programming - 새로운 함수나 객체를 만들어야 하는지 결정하는 데에 같은 기술을 사용한다. 그것은 단일 책임 원칙 (single responsibility principle) 인데, 컴포넌트는 이상적으로 한 가지 일만 해야한다는 것이다. 만약 컴포넌트가 커진다면, 더 작은 컴포넌트로 분리되어야 한다.

  • CSS - 무엇을 class 선택자로 만들지 고려하라. (컴포넌트가 조금 더 세분화되어 있다.)

  • design - 디자인 계층을 어떻게 나열할 것인지 고려할 것.

JSON이 잘 설계되어있다면 UI의 컴포넌트 구조에 자연스럽게 매핑되는 경우가 많은 것을 볼 수 있는데, UI와 데이터 모델이 같은 정보 구조를 갖고 있기 때문이다. 각 컴포넌트를 하나의 데이터 모델과 일치하는 지점에서 UI를 컴포넌트로 나눈다.

여기 다섯개의 컴포넌트가 있다.

  1. FilterProductTable : 전체 앱을 감싼다.
  2. SearchBar : 유저의 입력값을 받는다.
  3. ProductTable : 유저의 입력값에 따라 리스트를 필터링하고 보여준다.
  4. ProductCategoryRow : 각 카테고리의 제목을 보여준다.
  5. ProductRow : 각 상품을 열으로 보여준다.

ProductTable 의 이름과 가격이 붙어있는 표 제목은 그 자체의 컴포넌트가 아니다. 이건 취향차이인데, 다른 방법으로 해도 된다. 예를 들면 그것은 ProductTable의 목록 안에 함께 보여지기 때문에 ProductTable 안에 있지만, 이 제목이 정렬 기능이 추가되어 복잡해지면 ProductTableHeader 라는 이름의 컴포넌트로 분리할 수 있다.

mockup의 컴포넌트를 식별했으니 이제는 계층 구조로 정렬할 수 있다. 다른 컴포넌트 내부에 나타나는 컴포넌트들은 자식 계층으로 나눌 수 있다.

  • FilterableProductTable
    • SearchBar
    • ProductTable
      • ProductCategoryRow
      • ProductRow

Step 2: Build a static version in React

이제 컴포넌트 계층을 가지게 되었으니, 앱을 구현할 차례이다. 가장 간단한 방법은 상호 작용을 추가하지 않고 데이터 모델에서 UI 를 렌더링하는 것을 구축하는 것이다. 먼저 정적인 버전을 만들고 이후에 상호작용을 추가하는 편이 제법 쉽다. 정적인 버전을 만드는 것은 생각 없이 많은 양의 타이핑만을 필요로하지만, 상호작용을 더하는 것은 많은 양의 타이핑은 필요 없지만 깊은 생각을 필요로 한다.

데이터 모델을 렌더링하는 정적인 버전의 앱을 만들기 위해서 당신은 다른 컴포넌트에서 재사용 되는 컴포넌트를 만들고 데이터를 props을 통해서 전달 해 줄 것이다. Props 는 부모에서 자식으로 데이터를 전달해주는 방식이다. (state 개념에 익숙하다면, 정적인 버전을 만드는 데에 state를 사용하지 말라. state는 상호작용 즉, 시간에 따라 변경되는 데이터에 대해서만 사용되어야 한다. 때문에 정적인 버전에서는 필요하지 않다.)

높은 계층의 컴포넌트 부터 시작하는 "하향식(top down)"이나 (FilterableProductTable) 아래 계층 컴포넌트부터 작동하는 "상향식(bottom up)" (ProductRow) 어느것이든 사용하여 만들 수 있다. 간단한 예에서는 일반적으로 하향식으로 진행하는 것이 더 쉽고, 큰 프로젝트에서는 상향식으로 진행하는 것이 더 쉽다.

컴포넌트를 만든 후에 데이터 모델을 렌더링하는 재사용 가능한 구성 요소 라이브러리를 가질 수 있다. 이것은 정적 앱이기 때문에 컴포넌트는 JSX만 반환할 것이다. 계층 맨 위에 있는 컴포넌트(FilterableProductTable)는 데이터 모델을 props로 사용한다. 데이터가 최상위 구성 요소에서 트리의 맨 아래에 있는 컴포넌트로 흐르기 때문에 이를 단방향 데이터 흐름(one-way data flow)이라고 한다.

Step 3: Find the minimal but complete representation of UI state

UI를 상호작용하게 하기 위해서는 유저가 데이터 모델을 바꿀 수 있게 해야한다. 이것을 위해 state를 사용할 것이다.

앱이 기억해야 하는 최소한의 세트를 state라고 생각해라. 가장 중요한 원칙은 state를 DRY하게 두라는 것이다. (Dont Repeat Yourself / 반복하지 말아라) 앱이 필요로 하고 필요에 따라 모든 것을 계산할 수 있는 최소한의 상태를 찾아내야 한다. 예를 들어, 만약 쇼핑 리스트를 만든다면, 배열 안에 아이템들을 state로 저장할 것이다. 리스트의 아이템들의 숫자를 보여주고 싶다면, 아이템들의 숫자는 다른 state를 만들어 저장하지 말고, 그저 배열의 길이를 세도록 해야 한다.

다음 예시 앱에서 모든 데이터들을 생각해 보아라.

  1. 물품들의 원본 리스트
  2. 유저가 입력한 검색 텍스트
  3. 체크박스의 값
  4. 물품들의 필터링된 리스트

이 중 어떤 것이 state인지 구분해 보아라.

  • 시간이 지나도 변하지 않고 남아있나? 그렇다면, state가 아니다.
  • 부모로부터 props로 전달되었나? 그렇다면, state가 아니다.
  • 컴포넌트 안에 이미 존재했던 state나 props를 기반으로 계산할 수 있나? 그렇다면 완전히 state가 아니다!

하나 남은 것이 바로 state일 것이다.
하나씩 차례로 다시 보면,

  1. 원본 리스트는 props로 전달되기 때문에 state가 아니다.
  2. 검색 텍스트는 계속 변하고 어떤 것으로부터도 계산할 수 없기에 state인 것 같다.
  3. 체크 박스의 값은 계속 변하고 어떤 것으로부터도 계산할 수 없기에 state인 것 같다.
  4. 물품의 필터링된 목록은 원본 리스트에서 가져와서 검색 텍스트나 체크박스로 계산되어지기 때문에 state가 아니다.

따라서 검색 텍스트와 체크박스의 값만 state이다!

Step 4: Identify where your state should live

최소한의 state 데이터를 알아냈다면, 어떤 컴포넌트가 이 state를 변화시키거나 이 state를 소유하는지도 알아야 한다. 기억할 것은 : React는 데이터를 컴포넌트 상위 계층인 부모로부터 자식 컴포넌트로 내려주는, 단방향의 데이터 흐름을 가지는 것이다. 어떤 컴포넌트가 어떤 state를 가지는 지 명확하지 않을 수 있다. 이 개념에 익숙하지 않다면 어려울 수 있으나, 다음 단계를 따라하면 알아낼 수 있을 것이다.

앱에서 각 state에 대해 다음을 수행한다 :

  1. state를 기반으로 무언가를 렌더링하는 모든 컴포넌트를 식별해라.
  2. 계층에서 가장 가까운 그들의 공통적인 부모 컴포넌트를 찾아라.
  3. state가 어디에 있어야 할 지 정해라.
  • 때때로 state를 공통적인 부모 컴포넌트에 직접 넣을 수 있다.
  • 그들의 공통적인 부모 컴포넌트 위의 컴포넌트에도 둘 수 있다.
  • state를 어디에 두어야할지 모르겠다면, state를 가지기만 하는 새 컴포넌트를 생성하고 공통 부모 컴포넌트 위의 계층 어딘가에 추가할 수도 있다.

이전 단계에서 이 앱의 2가지 state, 검색 텍스트와 체크박스의 값이라는 state들을 찾았다. 이 예제에서 두 가지는 항상 같이 나타날 것이므로 같은 위치에 배치하는 것이 좋다.

이제 이것들을 위한 전략을 살펴보면,

  1. state를 사용할 컴포넌트를 식별하기.
  • ProductTable은 상태(검색 텍스트와 체크박스 값)를 기반으로 하는 물품들을 필터링 해야한다
  • SearchBar는 state(검색 텍스트와 체크박스 값)를 보여줘야 한다
  1. 그들의 공통 부모를 찾기 : 두개의 컴포넌트가 공유하는 첫 부모 컴포넌트는 FilterableProductTable 이다.

  2. state를 어디에 둘 지 결정하기 : FilterableProductTable 에 필터 텍스트와 체크 state 값을 둘 것이다.

그래서 state 값은 FilterableProductTable에 둘 수 있게 됐다.

useState() 훅을 사용하여 컴포넌트에 state를 추가한다. 훅은 리액트에 "hook into"할 수 있게 하는 특별한 함수이다. 두개의 state 변수를 FilterableProductTable에 등록하고 그들의 초기상태를 지정한다.

function FilterableProductTable({ products }) {
  const [filterText, setFilterText] = useState('');
  const [inStockOnly, setInStockOnly] = useState(false);

그 후 ProductTableSearchBar에 props로 넘겨준다.

<div>
  <SearchBar 
    filterText={filterText} 
    inStockOnly={inStockOnly} />
  <ProductTable 
    products={products}
    filterText={filterText}
    inStockOnly={inStockOnly} />
</div>

Step 5: Add inverse data flow

이제 앱은 props와 state로 아래 계층으로 흐르면서 잘 렌더링 된다. 그러나 사용자 입력에 따라 상태를 변경하려면 계층 구조의 깊은 곳에 있는 양식 구성 요소가 FilterableProductTable에서 state를 업데이트해야 하는 다른 데이터 흐름이 필요하다.

리액트는 이 데이터 흐름을 명시적으로 만들지만 양방향 데이터 바인딩 보다는 좀 더 많은 타이핑을 필요로 한다. 위의 예제에서 타이핑을 하거나 체크박스를 체크할 때, 리액트는 input을 무시하는 것을 알 수 있다. 이것은 의도된 것이다. <input value={filterText} />라고 적음으로써 inputvalue prop 을 항상 FilterableProductTable에서 전달 받은 filterText상태와 항상 같게 설정했다.filterText는 set된 적이 없기 때문에 input은 변하지 않을 것이다.

사용자가 양식 입력을 변경할 때마다 state가 업데이트되어 변경 사항을 반영하도록 만들고 싶을 것이다. state는 FilterableProductTable이 소유하므로, setFilterTextsetInStockOnly만이 호출할 수 있다. SearchBarFilterableProductTable를 업데이트 하게 하려면 아래와 같이 넘겨주어야 한다.

function FilterableProductTable({ products }) {
  const [filterText, setFilterText] = useState('');
  const [inStockOnly, setInStockOnly] = useState(false);

  return (
    <div>
      <SearchBar 
        filterText={filterText} 
        inStockOnly={inStockOnly}
        onFilterTextChange={setFilterText}
        onInStockOnlyChange={setInStockOnly} />

SearchBar안에 onChange 라는 이벤트 핸들러를 더해 부모 컴포넌트에 있는 state를 set 할 수 있다.

<input 
  type="text" 
  value={filterText} 
  placeholder="Search..." 
  onChange={(e) => onFilterTextChange(e.target.value)} />

이제 완성!


🍀 느낀 점

굉장히 길었지만, 상태를 어떻게 만들고 관리하는지에 대해 예제와 함께 자세히 나와있어 열심히 읽었다. 상태를 어디에 위치해야 하는지 고민이 된 적이 있었는데, 앞으로는 이 5단계를 숙지하여 깔끔한 앱을 만들어 보도록 해야겠다!

원문 : https://react.dev/learn/thinking-in-react

profile
기록하는 습관을 기르고 있습니다.

0개의 댓글