React 사고하기: 필터링 테이블 구현

리액트로 사고하기

간단한 필터링 테이블 구현하기

  • 리액트 컴포넌트 쪼개기
  • 정적 UI 구현
  • State 선언하기
  • State 업데이트하기

리액트v19 공식문서-리액트로 사고하기


1️⃣ UI를 컴포넌트 계층으로 쪼개기

  • 단일 책임 원칙을 고려해서 나누기(모든 클래스는 단 하나의 책임만 가져야 한다)
    • 컴포넌트가 한 가지 작업만을 수행하도록 컴포넌트를 분리
    • 너무 많은 작업을 수행하는 컴포넌트는 더 작은 컴포넌트로 나눔
    • 주요 컴포넌트 기능과 관련 없는 코드를 별도의 유틸리티 함수로 추출
    • 관련 있는 기능들을 커스텀 Hook으로 캡슐화
  • 컴포넌트의 재활용성 고려하기
  • 컴포넌트를 나누고 계층 구조로 정리해보기

🥑 냉장고 재료 관리 테이블

FilterableStockList
└── SearchBar // 검색, 필터링 기능
└── StockList // 필터링된 재료 리스트가 보여지는 뷰
    └── ItemTable // 카테고리별(음료, 야채, 단백질 등) 테이블
    └── ItemRow // 아이템 별 Row 컴포넌트


2️⃣ React로 정적인 버전 구현하기

  • 기능을 추가하지 않고 데이터 모델로부터 UI를 렌더링하는 정적인 버전 구현
    • props를 이용해 데이터를 넘겨주는 컴포넌트 구현
    • 단방향 데이터 흐름
    • 컴포넌트를 명확히 분리하고 리스트 렌더링같은 경우 간결하고 명확하게 코드를 짜는 것 연습 필요함
  • 각 컴포넌트에서 필요한 데이터 생각해보기
FilterableStockList // 전체 재료 목록
└── SearchBar // 검색 입력값, 체크 여부
└── StockList // 필터링된 재료 목록
    └── ItemTable // 필터링된 재료 목록 + 그룹화 대상 카테고리
    └── ItemRow // 재료 이름, 재고


3️⃣ 최소한의 데이터만 이용해서 완벽하기 ui state 표현하기

  • 리액트는 state를 통해 사용자가 데이터를 변경할 수 있다.
  • state는 앱이 기억해야 하는, 변경할 수 있는 데이터의 최소 집합
  • state를 구조화하는데 가장 중요한 원칙은 중복배제원칙!
  • 애플리케이션이 필요로 하는 가장 최소한의 state를 파악하고 나머지 모든 것들은 필요에 따라 실시간으로 계산하기
    • 시간이 지나도 변하지 않는지?
    • 부모로부터 props를 통해 전달되는지?
    • 컴포넌트 내 다른 state나 props를 가지고 계산할 수 있는 값인지?
FilterText(검색 입력값), InStockOnly(체크여부)
=> 사용자가 변경할 수 있는 데이터, 다른 값을 이용해서 계산할 수 없음
=> State로 관리

filteredItems(필터링된 리스트)
=> 전체 리스트를 FilterText와 InStockOnly를 이용해 계산할 수 있는 값
=> State 아님

📖Props vs State
리액트는 props와 state라는 두 개의 데이터 ‘모델’이 존재한다.
props는 함수를 통해 전달되는 인자 같은 성격을 가진다.
state는 컴포넌트의 메모리 같은 성격을 가진다.


4️⃣ State가 어디에 있어야 할 지 정하기

리액트는 항상 컴포넌트 계층 구조를 따라 부모에서 자식으로 데이터를 전달하는 단방향 데이터 흐름을 사용한다.

  1. state값을 사용하는 컴포넌트 모두 찾기 ⇒ SearchBar StockList
  2. 대상 컴포넌트의 공통 부모 찾기 ⇒ FilterableStockList
  3. 공통 부모 컴포넌트에서 해당 state를 소유하고 관리하며 props로 자식 컴포넌트에 전달


5️⃣ 역 데이터 흐름 추가하기

자식 컴포넌트에서 이벤트(ex. 사용자 입력)에 따라 state를 변경하려면 반대 방향의 데이터 흐름을 만들어야 한다. ⇒ state값을 관리하는 부모 컴포넌트로 부터 setState함수를 전달받아 이벤트 핸들러에 연결한다.(실제로 데이터가 역방향으로 이동하는건 아님,, 부모로부터 state를 업데이트하는 함수를 받아 업데이트할 뿐)

  • state 값을 업데이트하는 onFilterTextChange, onInStockOnlyChange 함수를 SearchBar 컴포넌트 에 props로 전달

FilterableStockList.tsx

function App() {
  const fridgeItems: Inventory[] = [...FRIDGE_ITEMS];
  const [filterText, setFilterText] = useState < string > "";
  const [inStockOnly, setInStockOnly] = useState < boolean > false;

  return (
    <div className="flex flex-col items-center min-h-svh">
      <div className="w-100 mt-20">
        <SearchBar
          filterText={filterText}
          inStockOnly={inStockOnly}
          onFilterTextChange={setFilterText}
          onInStockOnlyChange={setInStockOnly}
        />
        <div>
          <StockList
            fridgeItems={fridgeItems}
            filterText={filterText}
            inStockOnly={inStockOnly}
          />
        </div>
      </div>
    </div>
  );
}

SearchBar.tsx

import { Input } from '@/components/ui/input'
import { Checkbox } from '@/components/ui/checkbox'

interface SearchBarProps {
  filterText: string
  inStockOnly: boolean
  onFilterTextChange: (text: string) => void
  onInStockOnlyChange: (checked: boolean) => void
}

const SearchBar: React.FC<SearchBarProps> = ({
  filterText,
  inStockOnly,
  onFilterTextChange,
  onInStockOnlyChange,
}) => {
  return (
    <div>
      <Input
        type='text'
        placeholder='재료를 검색하세요'
        value={filterText}
        onChange={(e) => onFilterTextChange(e.target.value)}
      />
      <div className='mt-2'>
        <Checkbox
          id='showOnlyAvailable'
          checked={inStockOnly}
          onCheckedChange={(checked) => onInStockOnlyChange(!!checked)}
        />
        <label htmlFor='showOnlyAvailable' className='text-sm font-medium ml-1'>
          재고가 있는 것만 보기
        </label>
      </div>
    </div>
  )
}

export default SearchBar

Categories:

Updated:

Leave a comment