App.jsx 라인별 상세 설명

이 문서는 App.jsx가 공공데이터포털 오픈 API를 호출하고, 응답받은 JSON 데이터를 React 화면에 출력하는 과정을 라인별로 설명합니다.

기준일: 20260908. 현재 코드의 API는 경찰청_전국 범죄 발생 및 검거 현황_20241231 데이터를 조회합니다.

데이터 형식 JSON 또는 XML. 현재 코드는 returnType=JSON을 사용합니다.
Base URL https://api.odcloud.kr/api
Swagger URL https://infuser.odcloud.kr/oas/docs?namespace=15064217/v1
인증 방식 serviceKey 쿼리 파라미터로 일반 인증키를 전달합니다.

전체 흐름

컴포넌트가 처음 화면에 나타나면 useEffect가 한 번 실행됩니다. 그 안에서 API 주소와 검색 조건을 합쳐 fetch로 요청하고, 응답 JSON의 data 배열을 React 상태에 저장합니다. 상태가 바뀌면 React가 화면을 다시 그려 각 항목을 pre 태그로 출력합니다.

라인별 설명

라인 코드 상세 설명
1 import { useEffect, useState } from 'react' React에서 제공하는 Hook 두 개를 가져옵니다. useState는 화면에 표시할 데이터를 상태로 저장할 때 쓰고, useEffect는 컴포넌트가 처음 실행될 때 API 요청 같은 부수 효과를 처리할 때 씁니다.
3-9 // 데이터포맷 // JSON+XML // Base URL // api.odcloud.kr/api // Swagger URL // https://infuser.odcloud.kr/oas/docs?namespace=15064217/v1 // 일반 인증키 // a73690547c7e3fc6101f1f2d6bd2dfc9f4a453270aa6287eb52feb203c92cb98 dataFormat.md에 있던 API 정보를 주석으로 옮겨둔 부분입니다. 주석은 실행되지 않지만, 이 코드가 어떤 공공기관 API를 기준으로 작성됐는지 개발자가 바로 확인할 수 있게 해 줍니다.
11-12 const API_URL = 'https://api.odcloud.kr/api/15064217/v1/uddi:4f4a7f28-e408-4df4-9230-ebbe4c524dcf' 실제 데이터를 요청할 API 주소를 상수로 저장합니다. 기존에 https://api.odcloud.kr/api/...처럼 점 세 개가 들어가 있으면 실제 endpoint가 아니기 때문에 데이터를 불러올 수 없습니다. 15064217/v1은 Swagger namespace이고, uddi:4f4a7f28-e408-4df4-9230-ebbe4c524dcf는 2024년 기준 데이터의 고유 API 경로입니다.
14-15 const SERVICE_KEY = 'a73690547c7e3fc6101f1f2d6bd2dfc9f4a453270aa6287eb52feb203c92cb98' 공공데이터포털 API를 호출할 때 필요한 인증키를 상수로 저장합니다. 아래에서 serviceKey라는 쿼리 파라미터로 전달됩니다.
17 function App() { React 컴포넌트 App을 선언합니다. 이 함수가 반환하는 JSX가 브라우저 화면에 렌더링됩니다.
18 const [data, setData] = useState([]) API에서 받아온 목록 데이터를 저장하는 상태입니다. 처음에는 아직 데이터가 없으므로 빈 배열 []로 시작합니다. setData를 호출하면 화면이 다시 렌더링됩니다.
19 const [loading, setLoading] = useState(true) API 요청이 진행 중인지 나타내는 상태입니다. 처음 화면이 열리면 바로 데이터를 가져와야 하므로 true로 시작합니다.
20 const [error, setError] = useState(null) API 요청 중 문제가 생겼을 때 에러 메시지를 저장하는 상태입니다. 처음에는 에러가 없으므로 null입니다.
22 useEffect(() => { 컴포넌트가 렌더링된 뒤 실행할 코드를 작성합니다. 이 코드에서는 API 요청을 시작합니다. 아래쪽 의존성 배열이 비어 있기 때문에 최초 1회만 실행됩니다.
23 const controller = new AbortController() API 요청을 중간에 취소할 수 있게 해 주는 객체를 만듭니다. 사용자가 화면을 빠르게 벗어나 컴포넌트가 사라질 때 불필요한 네트워크 요청을 정리하는 데 사용합니다.
24-30 const params = new URLSearchParams({ page: '1', perPage: '10', returnType: 'JSON', serviceKey: SERVICE_KEY, }) API 주소 뒤에 붙일 쿼리 문자열을 안전하게 만듭니다. page는 조회할 페이지, perPage는 한 번에 가져올 개수, returnType은 응답 형식, serviceKey는 인증키입니다. 직접 문자열을 더하는 것보다 오타와 인코딩 문제를 줄일 수 있습니다.
32 // 20260908: dataFormat.md의 Swagger namespace(15064217/v1)에서 확인한 실제 공공데이터 endpoint로 호출합니다. 수정 날짜와 수정 이유를 남긴 주석입니다. 나중에 코드를 보는 사람이 왜 이 endpoint를 쓰는지 추적할 수 있습니다.
33-35 fetch(`${API_URL}?${params.toString()}`, { signal: controller.signal, }) 완성된 API URL로 HTTP 요청을 보냅니다. params.toString()은 객체 형태의 조건을 page=1&perPage=10&returnType=JSON&serviceKey=... 형태로 바꿉니다. signal은 위에서 만든 AbortController와 연결됩니다.
36 .then((response) => { 서버에서 응답이 도착하면 가장 먼저 실행되는 처리 단계입니다. 여기서 응답 상태가 정상인지 확인합니다.
37-39 if (!response.ok) { throw new Error(`API 요청 실패 (${response.status})`) } HTTP 상태 코드가 200번대가 아니면 실패로 처리합니다. 예를 들어 인증키가 틀리면 401, 서버 문제가 있으면 500 같은 값이 들어올 수 있습니다. throw를 실행하면 아래 catch 블록으로 이동합니다.
41 return response.json() 응답 본문을 JSON 객체로 변환합니다. 공공데이터 API가 내려주는 원본 문자열을 JavaScript에서 다룰 수 있는 객체로 바꾸는 단계입니다.
43-45 .then((result) => { setData(result.data ?? []) }) 변환된 JSON 결과를 받아 result.data 배열만 상태에 저장합니다. 공공데이터 API 응답은 보통 currentCount, data, matchCount, page, perPage, totalCount를 포함합니다. 화면에 필요한 목록은 data입니다. ?? []는 data가 없을 때 빈 배열로 대체해 화면 오류를 막습니다.
46-51 .catch((error) => { if (error.name !== 'AbortError') { console.error(error) setError(error.message) } }) 요청 실패, JSON 변환 실패, 네트워크 오류 등을 처리합니다. 단, 컴포넌트 정리 과정에서 의도적으로 요청을 취소한 AbortError는 사용자에게 에러로 보여주지 않습니다. 실제 오류만 콘솔에 출력하고 화면용 에러 상태에 저장합니다.
52-54 .finally(() => { setLoading(false) }) 성공하든 실패하든 요청이 끝나면 로딩 상태를 끕니다. 이 값이 false가 되면 더 이상 "불러오는 중..." 화면을 보여주지 않습니다.
56-58 return () => { controller.abort() } useEffect의 정리 함수입니다. 컴포넌트가 사라질 때 아직 끝나지 않은 fetch 요청을 취소합니다. React 개발 모드에서 effect가 재실행될 때도 이전 요청을 정리하는 역할을 합니다.
59 }, []) useEffect의 의존성 배열입니다. 빈 배열이면 컴포넌트가 처음 화면에 나타난 뒤 한 번만 API를 호출합니다.
61 if (loading) return <p>불러오는 중...</p> API 요청이 아직 끝나지 않았으면 목록 대신 로딩 메시지를 보여줍니다. 이 줄에서 return이 실행되면 아래의 실제 목록 화면은 아직 렌더링되지 않습니다.
62 if (error) return <p>에러: {error}</p> 에러 상태에 메시지가 있으면 데이터 목록 대신 에러 메시지를 보여줍니다. 예를 들어 잘못된 URL, 인증 실패, 서버 오류가 있을 때 이 화면이 표시됩니다.
64-66 return ( <div> <h1>경찰청 전국 범죄 발생 및 검거 현황</h1> 정상적으로 데이터 로딩이 끝났을 때 보여줄 JSX를 반환합니다. 가장 바깥의 div는 여러 요소를 묶는 컨테이너이고, h1은 페이지 제목입니다.
68 {data.map((item, index) => ( data 배열의 각 항목을 하나씩 화면 요소로 바꿉니다. React에서 목록을 출력할 때 자주 사용하는 패턴입니다.
69 <div key={`${item['범죄소분류']}-${index}`}> 각 목록 항목을 감싸는 div입니다. key는 React가 목록 변경을 효율적으로 추적하기 위해 필요합니다. 여기서는 범죄 소분류 값과 배열 인덱스를 합쳐 임시 고유값으로 사용합니다.
70 <pre>{JSON.stringify(item, null, 2)}</pre> 각 데이터 객체를 보기 좋게 문자열로 바꿔 출력합니다. JSON.stringify(item, null, 2)의 세 번째 인자 2는 들여쓰기 칸 수입니다. pre 태그는 줄바꿈과 공백을 그대로 보여줍니다.
71-73 </div> ))} 목록 항목 하나의 JSX와 map 반복문을 닫습니다. 데이터가 10개라면 이 구조가 10번 반복되어 화면에 표시됩니다.
74-76 </div> ) } 전체 화면을 감싸던 div, JSX 반환식, App 컴포넌트 함수를 순서대로 닫습니다.
78 export default App 다른 파일에서 App 컴포넌트를 가져다 쓸 수 있게 내보냅니다. main.jsx에서 이 컴포넌트를 import해 실제 DOM에 렌더링합니다.

API 응답 데이터 구조

필드 예상 의미 화면 사용 여부
currentCount 현재 응답에 포함된 데이터 개수입니다. 현재 화면에는 직접 출력하지 않습니다.
data 범죄 발생 및 검거 현황 목록 배열입니다. setData(result.data ?? [])로 저장해서 화면에 출력합니다.
matchCount 검색 조건에 맞는 데이터 개수입니다. 현재 화면에는 직접 출력하지 않습니다.
page 현재 페이지 번호입니다. 요청값은 1이고, 화면에는 직접 출력하지 않습니다.
perPage 페이지당 데이터 개수입니다. 요청값은 10이고, 화면에는 직접 출력하지 않습니다.
totalCount 전체 데이터 개수입니다. 현재 화면에는 직접 출력하지 않습니다.

각 범죄 데이터 항목의 주요 컬럼

컬럼 설명
범죄대분류 강력범죄, 절도범죄처럼 가장 큰 범죄 분류입니다.
범죄중분류 대분류 아래의 중간 범죄 분류입니다.
범죄소분류 살인, 영아살해, 존속살해처럼 더 구체적인 범죄 항목입니다.
발생건수 해당 범죄가 발생한 건수입니다.
검거건수 해당 범죄 중 검거된 건수입니다.
검거인원(남자) 검거된 인원 중 남자 인원입니다.
검거인원(여자) 검거된 인원 중 여자 인원입니다.
검거인원(미상) 성별 등 일부 정보가 확인되지 않은 검거 인원입니다.
법인체 검거 대상 중 법인체로 분류된 수입니다.

주의할 점

현재 SERVICE_KEY가 프론트엔드 코드에 그대로 들어 있습니다. 학습용 예제나 개인 실습에서는 괜찮지만, 실제 서비스에서는 브라우저 개발자 도구로 누구나 값을 볼 수 있습니다. 운영 서비스라면 서버를 거쳐 API를 호출하거나, 환경 변수와 백엔드 프록시를 사용하는 구조가 더 적절합니다.

App.jsx 전체 코드

import { useEffect, useState } from 'react'

// 데이터포맷
// JSON+XML
// Base URL
// api.odcloud.kr/api
// Swagger URL
// https://infuser.odcloud.kr/oas/docs?namespace=15064217/v1
// 일반 인증키
// a73690547c7e3fc6101f1f2d6bd2dfc9f4a453270aa6287eb52feb203c92cb98

const API_URL =
  'https://api.odcloud.kr/api/15064217/v1/uddi:4f4a7f28-e408-4df4-9230-ebbe4c524dcf'

const SERVICE_KEY =
  'a73690547c7e3fc6101f1f2d6bd2dfc9f4a453270aa6287eb52feb203c92cb98'

function App() {
  const [data, setData] = useState([])
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState(null)

  useEffect(() => {
    const controller = new AbortController()
    const params = new URLSearchParams({
      page: '1',
      perPage: '10',
      returnType: 'JSON',
      serviceKey: SERVICE_KEY,
    })

    // 20260908: dataFormat.md의 Swagger namespace(15064217/v1)에서 확인한 실제 공공데이터 endpoint로 호출합니다.
    fetch(`${API_URL}?${params.toString()}`, {
      signal: controller.signal,
    })
      .then((response) => {
        if (!response.ok) {
          throw new Error(`API 요청 실패 (${response.status})`)
        }

        return response.json()
      })
      .then((result) => {
        setData(result.data ?? [])
      })
      .catch((error) => {
        if (error.name !== 'AbortError') {
          console.error(error)
          setError(error.message)
        }
      })
      .finally(() => {
        setLoading(false)
      })

    return () => {
      controller.abort()
    }
  }, [])

  if (loading) return <p>불러오는 중...</p>
  if (error) return <p>에러: {error}</p>

  return (
    <div>
      <h1>경찰청 전국 범죄 발생 및 검거 현황</h1>

      {data.map((item, index) => (
        <div key={`${item['범죄소분류']}-${index}`}>
          <pre>{JSON.stringify(item, null, 2)}</pre>
        </div>
      ))}
    </div>
  )
}

export default App