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…PageStore—useEquipmentPageStore처럼 페이지의 화면 상태(선택한 행, 열린 탭 등)를 한곳에 보관use…Query—useTestReqQuery처럼 특정 데이터(주문 목록 등)를 가져오는useQuery호출을 한 함수로 정리useDebounce— 재고/장비 검색창에서 타이핑이 멈춘 뒤에만 서버를 조회해 요청 폭주를 막음
3.2 조건부 렌더링
상황에 따라 다른 화면을 보여주는 방법들입니다. JSX 의 { } 안에는 if 문을 직접 쓸 수 없어서,
값을 만들어 내는 식(&&·삼항 연산자 등)으로 화면을 고릅니다.
① && — "조건이 참일 때만 보여주기"
{isLoading && <p>불러오는 중…</p>}{count && <Badge />} 에서 count 가 0 이면 화면에 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 예요.
① 형제 사이에서 고유(전체에서 유일할 필요는 없음) ② 안정적(렌더마다 바뀌면 안 됨) ③ 보통 데이터의 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 |