React 기초 · 3장 커스텀 훅 · 렌더링

3장 · 커스텀 훅과 렌더링

커스텀 훅? 직접 만들기 → 조건부 렌더링? → 리스트 렌더링과 key? 순서로, 직접 실행하며 익힙니다.

3.1 커스텀 훅이란?

커스텀 훅(Custom Hook)?use 로 시작하는 내가 직접 만든 함수예요. 특별한 문법이 아니라 그냥 평범한 함수인데, 두 가지 특징이 있습니다.

  • 이름이 use 로 시작한다. (예: useToggle, useCounter)
  • 함수 안에서 다른 ? (useState?, useEffect? 등)을 부를 수 있다.
훅 규칙은 여기서도 그대로

맨 위에서 호출, if·반복문 안에서 호출 금지 — 이 규칙은 컴포넌트뿐 아니라 커스텀 훅 안에서도 똑같이 적용됩니다.

왜 만드나요?

여러 컴포넌트에서 똑같은 훅 로직이 반복될 때, 그걸 한 함수로 모아 재사용하기 위해서입니다. 컴포넌트 본문은 깔끔해지고, 로직은 한곳에서 관리돼요.

커스텀 훅은 자주 쓰는 재료를 미리 손질해 둔 밀키트. 컴포넌트는 꺼내 쓰기만 하면 됩니다.

아래는 useToggle 을 직접 만들어 버튼 on/off 에 쓰는 예제입니다. 버튼을 눌러보세요.

또 다른 예: useCounter

값을 돌려줄 때는 배열([on, toggle])이든 객체({ count, inc })든 자유예요. 여러 개를 돌려줄 땐 객체가 읽기 편합니다.

한 발 더: useDebounce

검색창에 글자를 칠 때마다 서버에 요청하면 너무 잦죠. 디바운스? 는 "입력이 멈춘 뒤 잠깐 기다렸다가 마지막 값 한 번만" 처리하는 기법입니다. 핵심은 clearTimeout 이 든 정리 함수? 예요. 입력 칸에 빠르게 타이핑하고, 아래 두 줄이 어떻게 다른지 보세요.

실제로 어디에 쓰나
  • useAuth — 로그인한 사용자 정보·권한을 어디서든 한 줄로 꺼냄: const { user, hasRole } = useAuth() (이 프로젝트 46회 사용)
  • useSearchForm — 사용자·장비·검수 목록 화면마다 반복되는 검색 폼 상태(검색어·필터·제출)를 공통 로직으로 묶음
  • use…PageStoreuseEquipmentPageStore처럼 페이지의 화면 상태(선택한 행, 열린 탭 등)를 한곳에 보관
  • use…QueryuseTestReqQuery처럼 특정 데이터(주문 목록 등)를 가져오는 useQuery 호출을 한 함수로 정리
  • useDebounce — 재고/장비 검색창에서 타이핑이 멈춘 뒤에만 서버를 조회해 요청 폭주를 막음

3.2 조건부 렌더링

상황에 따라 다른 화면을 보여주는 방법들입니다. JSX 의 { } 안에는 if 문을 직접 쓸 수 없어서, 값을 만들어 내는 식(&&·삼항 연산자 등)으로 화면을 고릅니다.

&& — "조건이 참일 때만 보여주기"

{isLoading && <p>불러오는 중…</p>}
흔한 함정

{count && <Badge />} 에서 count0 이면 화면에 0 이 그대로 찍힙니다. 숫자를 조건으로 쓸 땐 {count > 0 && <Badge />} 처럼 불리언으로 바꾸세요.

② 삼항 연산자 — "A냐 B냐"

{isLoggedIn ? <Dashboard /> : <LoginForm />}

③ early return — "조건 안 맞으면 일찍 끝내기"

function Profile({ user }) {
  if (!user) return <p>로그인이 필요합니다.</p>
  return <h1>{user.name} 님 환영합니다</h1>
}

null 반환 — "아무것도 안 그리기"

컴포넌트가 null 을 반환하면 화면에 아무것도 나오지 않습니다.

아래는 로그인 여부에 따라 다른 화면을 보여주는 예제입니다(삼항 + &&). 버튼을 눌러보세요.

방법언제 쓰나
{조건 && <X/>}참일 때만 보여주고, 아니면 안 보여줄 때
{조건 ? <A/> : <B/>}둘 중 하나를 골라 보여줄 때
early return함수 위쪽에서 예외 상황을 먼저 걸러낼 때
return null컴포넌트가 아무것도 안 그려야 할 때
실제로 어디에 쓰나
  • 권한 없으면 버튼 숨김 — 검수 결과 "승인" 버튼을 관리자에게만: {hasRole("ADMIN") && <ApproveButton />}
  • 로딩 중 스피너 — 장비 목록을 불러오는 동안 표 대신 로딩 표시: {isLoading ? <Spinner /> : <EquipmentTable />}
  • 데이터 없으면 "결과 없음" — 검색 결과가 빈 배열이면 "조회된 재고가 없습니다" 문구 표시
  • 에러 메시지 — 주문 저장이 실패하면 그때만 빨간 안내문 노출: {error && <ErrorText />}

3.3 리스트 렌더링과 key

배열을 화면에 여러 줄로 그릴 때는 .map() 으로 각 데이터를 JSX로 바꿉니다. 이때 각 요소에 key? 를 줘야 해요.

key 는 왜 필요할까?

리렌더? 가 일어나면 React는 새 화면과 이전 화면을 비교(재조정?)해서 바뀐 부분만 고칩니다. 리스트에서 어떤 항목이 추가·삭제·이동됐는지 정확히 알려면 각 항목에 고유한 표식이 필요한데, 그게 바로 key 예요.

key 규칙

형제 사이에서 고유(전체에서 유일할 필요는 없음) ② 안정적(렌더마다 바뀌면 안 됨) ③ 보통 데이터의 id 를 쓴다.
배열 index 를 key 로 쓰지 마세요. 순서가 바뀌거나 중간에 추가·삭제되면 index 가 다른 데이터를 가리켜 입력값이 엉키는 버그가 생깁니다.

아래는 배열을 .map() 으로 그리고, 항목을 추가하는 예제입니다. 각 <li>key={item.id} 를 줬어요.

실제로 어디에 쓰나
  • 표 목록 렌더 — 사용자·장비·재고 목록을 data.map((row) => <Tr key={row.id} />) 로 한 줄씩 그림
  • 동적 폼 줄 추가 — 주문서의 항목 목록처럼 "행 추가" 버튼으로 입력 줄을 늘림 (각 줄에 고유 key)
  • 셀렉트 옵션 — 공통코드·장비 종류 목록을 <option> 여러 개로 펼침
  • 탭·카드 반복 — 프로젝트 상세의 검수 항목들을 카드로 반복 표시

한눈에 정리

주제핵심
커스텀 훅use로 시작하는 내 함수. 반복 훅 로직을 모아 재사용
&& 렌더링참일 때만 표시 (숫자 0 주의!)
삼항 렌더링둘 중 하나 선택
early return / null일찍 끝내거나 아무것도 안 그리기
.map() + key배열을 여러 요소로, 각 항목엔 고유 id key