상태 세 개를 둡니다
data, loading, error를 분리하면 로딩 화면과 실패 문구를 조건별로 명확하게 렌더링할 수 있습니다.
`useFetch`는 제품 목록을 가져오는 코드가 컴포넌트 안에 흩어지지 않게 합니다. 특히 URL이 바뀌거나 컴포넌트가 사라질 때 이전 요청을 정리해야 화면이 늦은 응답에 흔들리지 않습니다.
data, loading, error를 분리하면 로딩 화면과 실패 문구를 조건별로 명확하게 렌더링할 수 있습니다.
fetch에 signal을 넘겨 url 변경이나 언마운트 시 진행 중인 요청을 중단할 수 있게 만듭니다.
정상 응답이면 JSON을 저장하고 ProductList는 실제 API 결과를 우선 사용합니다.
HTTP 실패와 네트워크 오류는 error 상태로 보내고, 취소된 요청은 일반 실패와 구분합니다.
성공과 실패 모두 loading을 false로 돌려 스피너가 남지 않도록 합니다.
loading이면 LoadingSpinner, error이면 실패 메시지, data가 있으면 API 결과를 렌더링합니다. API가 비어 있을 때는 목업 데이터로 실습을 계속할 수 있습니다.