농사로 API를 활용하여 식물별 난이도, 성장 속도, 광도, 습도에 따라 필터링 할 수 있는 서비스를 만들고 있다.

총 3개의 API를 불러올텐데, 이들은 동일한 cntntsNo(컨텐츠 번호) 항목을 가진다.

따라서 이들을 각각 불러와 cntntsNo을 기준으로 합치고, 매핑해보도록 한다.

 

 

 


 

 

 

API 호출 로직은 아래와 같다.

우선 실내정원용 식물 목록을 호출한다.

  const [gardenList, setGardenList] = useState<GardenItemProps[]>([]);
  const [loading, setLoading] = useState<boolean>(true);
  const [error, setError] = useState<string | null>(null);
  const [pageNo, setPageNo] = useState<number>(1); 

  const fetchGardenList = async () => {
    setLoading(true); //작업이 끝날 때까지 로딩
    try {
      const apiKey = process.env.REACT_APP_API_KEY;
      const parser = new XMLParser();

      const gardenListResponse = await axios.get(
        `http://localhost:8080/http://api.nongsaro.go.kr/service/garden/gardenList?apiKey=${apiKey}&numOfRows=3&pageNo=${pageNo}`
      );

      // XML을 JSON으로 변환
      const gardenListJson = parser.parse(gardenListResponse.data);
      const gardenListItems = gardenListJson?.response?.body?.items?.item || [];


      setGardenList(gardenListItems);
    } 
    catch (error) {
      console.error("Error fetching garden list:", error);
      setError("An error occurred while fetching the data.");
    } 
    finally {
      setLoading(false); //로딩 끝
    }
  };

 

 

이 글에서 자세히 설명하지는 않겠지만, XML을 JSON으로 변환하는 데에는 fast-xml-parser을 사용했다.

 

 

 

 

 

 

이제 실내정원용 식물 상세, 실내정원용 식물 첨부파일 목록도 똑같이 불러온다.

(가독성을 위해 타입 선언은 잠시 생략했다)

//상세정보 목록
  const gardenDetailResponse = await axios.get(
    `http://localhost:8080/http://api.nongsaro.go.kr/service/garden/gardenDtl?apiKey=${apiKey}&cntntsNo=${cntntsNo}`
  );

  const gardenDetailJson = parser.parse(gardenDetailResponse.data);
  const detailInfo = gardenDetailJson?.response?.body?.item || {};

//첨부파일 목록
  const gardenFileListResponse = await axios.get(
    `http://localhost:8080/http://api.nongsaro.go.kr/service/garden/gardenFileList?apiKey=${apiKey}&cntntsNo=${cntntsNo}`
  );

  const gardenFileListJson = parser.parse(gardenFileListResponse.data);
    let fileList = gardenFileListJson?.response?.body?.items?.item || [];

 

 

 

 

 

 

이것을 위 fetchGardenList 함수에 옮겨심는다.

  const [gardenList, setGardenList] = useState<GardenItemProps[]>([]);
  const [loading, setLoading] = useState<boolean>(true);
  const [error, setError] = useState<string | null>(null);
  const [pageNo, setPageNo] = useState<number>(1); 

  const fetchGardenList = async () => {
    setLoading(true); //작업이 끝날 때까지 로딩
    try {
      const apiKey = process.env.REACT_APP_API_KEY;
      const parser = new XMLParser();
      
      //실내정원용 식물 목록
      const gardenListResponse = await axios.get(
        `http://localhost:8080/http://api.nongsaro.go.kr/service/garden/gardenList?apiKey=${apiKey}&numOfRows=3&pageNo=${pageNo}`
      );
      // XML을 JSON으로 변환
      const gardenListJson = parser.parse(gardenListResponse.data);
      const gardenListItems = gardenListJson?.response?.body?.items?.item || [];
    
    
      //상세정보 목록
      const gardenDetailResponse = await axios.get(
        `http://localhost:8080/http://api.nongsaro.go.kr/service/garden/gardenDtl?apiKey=${apiKey}&cntntsNo=${cntntsNo}`
      );
      // XML을 JSON으로 변환
      const gardenDetailJson = parser.parse(gardenDetailResponse.data);
      const detailInfo = gardenDetailJson?.response?.body?.item || {};

      //첨부파일 목록
      const gardenFileListResponse = await axios.get(
        `http://localhost:8080/http://api.nongsaro.go.kr/service/garden/gardenFileList?apiKey=${apiKey}&cntntsNo=${cntntsNo}`
      );
      // XML을 JSON으로 변환
      const gardenFileListJson = parser.parse(gardenFileListResponse.data);
        let fileList = gardenFileListJson?.response?.body?.items?.item || [];

      setGardenList(gardenListItems);
    } 
    catch (error) {
      console.error("Error fetching garden list:", error);
      setError("An error occurred while fetching the data.");
    } 
    finally {
      setLoading(false); //로딩 끝
    }
  };

 

 

지금 fetchGardenList를 호출하여 gardenList를 확인하면 어떻게 될까?

당연히 실내정원용 식물 목록만 나온다.

마지막 setGardenList에 GardenListItems (가공된 GardenListResponse) 가 반환되도록 써두었기 때문이다.

 

 

 

이제 이 3개의 API로 불러들인 데이터를 합쳐보자.

 

gardenListItems의 각 항목을 item으로 받고, 이 중 공통으로 가지고 있는 값인 cntntsNo를 요청 변수에 넣어준다.

이 점을 토대로 map 메소드와 Promise.all을 실행하면 일종의 반복문이 수행된다.

 

Promise.all이란?

Promise.all은 여러 개의 비동기 작업을 병렬로 실행하고, 모든 작업이 완료될 때까지 기다리는 메소드이다.

모든 작업이 fufilled 상태여야만 성공으로 간주하고 각각의 작업을 배열로 반환한다.

반면 하나라도 실패한다면 rejected를 반환한다.

 

아래처럼 여러 개의 비동기 작업을 수행할 때, 유용하게 사용할 수 있다.

  const [gardenList, setGardenList] = useState<GardenItemProps[]>([]);
  const [loading, setLoading] = useState<boolean>(true);
  const [error, setError] = useState<string | null>(null);
  const [pageNo, setPageNo] = useState<number>(1); 


  const fetchGardenList = async () => {
    setLoading(true);
    try {
      const apiKey = process.env.REACT_APP_API_KEY;

      const gardenListResponse = await axios.get(
        `http://localhost:8080/http://api.nongsaro.go.kr/service/garden/gardenList?apiKey=${apiKey}&numOfRows=3&pageNo=${pageNo}`
      );

      const parser = new XMLParser();
      const gardenListJson = parser.parse(gardenListResponse.data);
      const gardenListItems = gardenListJson?.response?.body?.items?.item || [];

      //각 item에 대해 gardenDtl와 gardenFileList API를 동시에 호출하여 병렬 작업
      const detailedItems: GardenItemProps[] = await Promise.all(
        gardenListItems.map(async (item: any) => {
          const cntntsNo = item.cntntsNo;

          const gardenDetailResponse = await axios.get(
            `http://localhost:8080/http://api.nongsaro.go.kr/service/garden/gardenDtl?apiKey=${apiKey}&cntntsNo=${cntntsNo}`
          );

          const gardenDetailJson = parser.parse(gardenDetailResponse.data);
          const detailInfo: DetailInfoProps = gardenDetailJson?.response?.body?.item || {};

          const gardenFileListResponse = await axios.get(
            `http://localhost:8080/http://api.nongsaro.go.kr/service/garden/gardenFileList?apiKey=${apiKey}&cntntsNo=${cntntsNo}`
          );

          const gardenFileListJson = parser.parse(gardenFileListResponse.data);
          let fileList: FileItemProps[] =
            gardenFileListJson?.response?.body?.items?.item || [];
            
          if (!Array.isArray(fileList)) {
            fileList = [fileList];
          }

          return { //새로운 배열로 반환
            ...item,
            detailInfo,
            fileList,
          };
        })
      );

      setGardenList((prev) => [...prev, ...detailedItems]); //모든 데이터 묶기
    } 
    catch (error) {
      console.error("Error fetching garden list:", error);
      setError("An error occurred while fetching the data.");
    } 
    finally {
      setLoading(false);
    }
  };

 

 

 

 

코드 흐름

gardenListResponse를 호출하여 JSON으로 변환한 값을 gardenListItems에 저장한다.

나머지 2개의 호출을 각 gardenListItems의 개별 item의 cntntNo(식물 고유코드)에 매핑하여 새로운 배열을 만든다.

+ 이 때 fileList 데이터가 객체 형태인 경우가 있어, 별도로 배열화하는 로직을 추가했다.

마지막으로 기존 배열에 detailedItems 배열을 밀어넣어 3개 호출이 모두 포함된 배열을 완성한다.

 

 

 

 

 

 

 

+ Recent posts