Chap15. Http 요청 보내기

Muru·2023년 12월 12일

[React] 지식 저장소

목록 보기
20/30
post-thumbnail

15.1 : 이번 단원에선 무엇을 배우는가

지금까지 학습했던 것들은 모두 로컬에 있는 데이터로만 작업했었다.

이번단원에서는 로컬에있는 데이터가 아니라 외부의 데이터와 상호작용 하는 방식에 관련해서 학습한다.

  1. 리액트와 데이터베이스의 소통하는 방식에 대해서 학습한다.
  2. HTTP 요청과 응답을 처리하는 방법에 대하여 학습한다.


15.2 : 리액트와 데이터베이스의 직접 통신은 위험하다

“리액트”와 “데이터베이스”를 연결해서 데이터를 주고 받으면 되지 않을까?
이는 정말로 위험한 행위이다. 브라우저에서 실행되는 모든 자바스크립트 코드는
웹사이트의 이용자들도 접근하고 읽을 수 있기 때문이다. 데이터베이스에 담겨있는 정보가 노출되도 상관없는것이면 괜찮겠지만 데이터베이스에 접근을 허가하는 인증 정보, 개인정보 등 보안 이슈에 문제가 될 수 있기 때문이다.

그래서 직접적으로 통신하는것 대신에 다른 방법을 사용하는 것이 백엔드 어플리케이션이다.
이 백엔드 어플리케이션(NodeJs, PHP 등)는 브라우저 안에서 실행되지 않고 별도의 서버에서 실행 되는것이다. 별도의 서버에 인증 정보가 있으므로 웹사이트 이용자는 이 코드를 볼 수 없다.



15.3 : get 요청 보내기

Fetch Movies 버튼을 클릭하면 영화목록들을 출력해보자

영화진흥위원회에서 제공하는 API를 사용하여 학습의 목적으로 사용하였음을 밝힙니다.

영화진흥위원회 사이트

다음과 같이 영화 목록들이 출력이된다.
영화제목, 영화개봉일, 장르

사용할 컴포넌트는 세개로
데이터를 가져올 App.js
데이터 목록 출력의 중간다리 역할 MoviesList.js
데이터 목록 출력의 최종적인 역할 Movie.js
“//”를 사용하여 주석처리 한곳을 중점적으로 학습하자.

fetch 사용방법

fetch(‘요청할 url 주소’)				// 주소로부터 응답 객체를 받는다.
.then(응답객체 => 응답객체.json()) 	// 응답을 받고, json() 메소드로 파싱한 json()값을 리턴한다.
.then(응답객체 => do something		// json()값을 리턴받은것으로 원하는대로 처리한다.

App.js

import React, {useState} from 'react';
import MoviesList from './components/MoviesList';
import './App.css';

function App() {
  const [ movies, setMovies] = useState([]);

  function fetchMoviesHandler() {

// 주소로부터 응답 객체를 받는다.  
 fetch('http://kobis.or.kr/kobisopenapi/webservice/rest/movie/searchMovieList.json?key=f5eef3421c602c6cb7ea224104795888')
   
// 응답객체를 json() 메소드를 통해 json()값을 리턴한다.
.then((response) => {
    return response.json();
   })
// json()값을 원하는대로 처리한다.
.then((data) => {
    console.log(data);
    console.log(data.movieListResult.movieList)
    const transformedMovies = data.movieListResult.movieList.map(movieData => {
     return {
      key : movieData.movieCd,
      moviName : movieData.movieNm,
      openDate : movieData.openDt,
      genre : movieData.genreAlt
     };
    });
    setMovies(transformedMovies);
   });
  }

  return (
   <React.Fragment>
    <section>
     <button onClick={fetchMoviesHandler}>Fetch Movies</button>
    </section>
    <section>
// useState로 관리하고있는 movies 객체를 “MoviesList“에 “movies“ props로 전달.
     <MoviesList movies={movies} />
    </section>
   </React.Fragment>
  );
}

export default App;

MoviesList.js

import React from 'react';
import Movie from './Movie';
import classes from './MoviesList.module.css';

const MovieList = (props) => {
  return (
   <ul className={classes['movies-list']}>
    {props.movies.map((movie) => (
     <Movie
      key={movie.key}
      name={movie.moviName}
      openDate={movie.openDate}
      genre={movie.genre}
     />
    ))}
   </ul>
  );
};

export default MovieList;

Movie.js

import React from 'react';
import classes from './Movie.module.css';
const Movie = (props) => {
  return (
   <li className={classes.movie}>
    <h2>{props.name}</h2>
    <h3>{props.openDate}</h3>
    <p>{props.genre}</p>
   </li>
  );
};

export default Movie;


15.4 : 비동기/대기 사용하기

  • 자바스크립트 짤막 문법 지식
    async/await는 프로미스를 기반으로 동작한다.
    async/awiat를 사용하면 프로미스의 then/catch/finally 후속 처리 메서드에 콜백 함수를 전달해서 비동기 처리 결과를 후속 처리할 필요 없이 마치 동기 처리처럼 프로미스를 사용할 수 있다.

위의 15.3에서 알아보았던 get요청을 받을때 후속처리로 then을 두번 썻던것을 async/awiat를 사용하여 동기 처리하는것처럼 프로미스를 사용하는것이 가능하다.
가독성도 훨씬 좋아질 수 있다.

const [ movies, setMovies] = useState([]);

  async function fetchMoviesHandler() {
   const response = await fetch('http://kobis.or.kr/kobisopenapi/webservice/rest/movie/searchMovieList.json?key=f5eef3421c602c6cb7ea224104795888')
   const data = await response.json();
 
    const transformedMovies = data.movieListResult.movieList.map(movieData => {
     return {
      key : movieData.movieCd,
      moviName : movieData.movieNm,
      openDate : movieData.openDt,
      genre : movieData.genreAlt
     };
    });
    setMovies(transformedMovies);
   };

async/awiat를 사용했더니 코드가 훨씬 간결해진것을 볼 수 있다.



15.5 : 로딩 및 데이터 State 처리하기

특정 버튼을 클릭했을때 화면이 출력한다고 가정해보자. 그 화면이 출력되는 동안에 로딩 메시지나 이미지같은것을 출력할 수 있을까?
이는 상태관리를 통해 할 수 있다.

app.js

function App() {
  const [ movies, setMovies] = useState([]);
  const [ isLoading, setIsloading] = useState(false);

  async function fetchMoviesHandler() {
   // 로딩 상태의 불린값을 true로 (로딩중)
   setIsloading(true);
   const response = await fetch('http://kobis.or.kr/kobisopenapi/webservice/rest/movie/searchMovieList.json?key=f5eef3421c602c6cb7ea224104795888')
   const data = await response.json();
 
    const transformedMovies = data.movieListResult.movieList.map(movieData => {
     return {
      key : movieData.movieCd,
      moviName : movieData.movieNm,
      openDate : movieData.openDt,
      genre : movieData.genreAlt
     };
    });
    setMovies(transformedMovies);
    // 로딩 상태의 불린값을 false로 (로딩끝)
    setIsloading(false);
   };
  

  return (
   <React.Fragment>
    <section>
     <button onClick={fetchMoviesHandler}>Fetch Movies</button>
    </section>
    <section>
     {/* 로딩끝일때 영화 목록 출력 */}
     {!isLoading &&movies.length>0 &&<MoviesList movies={movies} />}
     {/* 로딩끝인데 표시된 영화 목록이 없을때 메시지 출력 */}
     {!isLoading &&movies.length === 0 &&<p></p>}
     {/* 로딩중일때 로딩중 메시지 출력 */}
     {isLoading &&<p> 로딩중... </p>}
    </section>
   </React.Fragment>
  );
}



15.6 : HTTP 오류 처리하기

HTTP 요청을 했음에도 불구하고 모종의 이유로 오류가 발생 할 수 있다.
이러한 오류에 대응하는 처리를 해보자.

오류에 대응하는 처리 역시 State로 관리한다.
fetch 부분에 의도적으로 잘못된 경로를 기입해보자.

  • fetch는 실제 오류코드를 받아도 오류로 처리하지 않는다. 오직 가져오지 않은 데이터에 대해 map을 하는등의 처리들만이 오류로 처리한다.
  • axios(서드 파티 라이브러리)를 사용한다면 실제 오류코드 받으면 오류로 처리한다.

해당 단원에서는 fetch API를 사용하였으므로
fetch API를 사용하여 오류 처리를 하도록 해보자.

app.js

import React, {useState} from 'react';
import MoviesList from './components/MoviesList';
import './App.css';

function App() {
  const [ movies, setMovies] = useState([]);
  const [ isLoading, setIsloading] = useState(false);
  const [ error, setError ] = useState(null);

  async function fetchMoviesHandler() {
   setIsloading(true);
   	// 이전에 받을 수 있던 에러를 초기화
   setError(null);

   	// try - catch 문으로 에러처리.
   try {
    const response = await fetch('https://swapi.dev/api/films/');
     
    if(!response.ok) {
     throw new Error('에러가 발생 했습니다.');
    }
    
    const data = await response.json();
    console.log(response);


     const transformedMovies = data.movieListResult.movieList.map(movieData => {
      return {
       key : movieData.movieCd,
       moviName : movieData.movieNm,
       openDate : movieData.openDt,
       genre : movieData.genreAlt
      };
     });
     setMovies(transformedMovies);
    } catch (error) {
     setError(error.message); 
    }
    setIsloading(false);
   }

  return (
   <React.Fragment>
    <section>
     <button onClick={fetchMoviesHandler}>Fetch Movies</button>
    </section>
    <section>
     {/* 로딩끝일때 영화 목록 출력 */}
     {!isLoading &&movies.length>0 &&<MoviesList movies={movies} />}
     {/* 로딩끝인데 표시된 영화 목록이 없을때 메시지 출력 */}
     {!isLoading &&movies.length === 0 &&!error && <p></p>}
     {/* 로딩끝이지만 에러를 받아왔을때 */}
     {!isLoading &&error &&<p> {error} </p>}
     {/* 로딩중일때 로딩중 메시지 출력 */}
     {isLoading &&<p> 로딩중... </p>}
    </section>
   </React.Fragment>
  );
}

export default App;

올바른 에러창을 띄워주는것을 볼 수 있다.

추가로 JSX 코드가 장황한것을 볼 수 있는데, 좀 더 클린하게끔 다듬어보자.

// 기본적으로 출력할 content 변수를 추가하자.
   let content = <p>Found no moives.</p>
   
// 이후로는 조건에따라 content 변수가 바뀔 수 있다. 
   if (movies.length > 0 ) {
    content = <MoviesList movies={movies} />
   }
   if (error) {
    content = <p>{error}</p>
   }
   if (isLoading) {
    content = <p>Loading...</p>
   }
 
 // 좀더 깔끔해진 JSX 코드
  return (
   <React.Fragment>
    <section>
     <button onClick={fetchMoviesHandler}>Fetch Movies</button>
    </section>
    <section>
     {content}
    </section>
   </React.Fragment>
  );
}

JSX 코드부분이 좀더 클린 한것을 볼 수 있다.



15.7 : 요청에 useEffect() 사용하기 (with useCallback)

fetch 버튼을 누르지 않고 사이트에 들어가자마자 바로 데이터를 들여오는 방법은 없을까?

useEffect()를 사용하자. (with useCallback)

App.js

import React, { useState, useEffect, useCallback } from "react";
import MoviesList from "./components/MoviesList";
import "./App.css";

function App() {
  const [movies, setMovies] = useState([]);
  const [isLoading, setIsloading] = useState(false);
  const [error, setError] = useState(null);

//  useEffect를 사용하여 해당 함수를 의존성 배열에 둔다면 문제가 있다.
//  함수는 객체기때문에 매 실행마다 참조값이 달라진다는것을 학습했었다. 
//  함수가 의존성 배열에 있다면 매 실행마다 참조값이 바뀌므로 무한 루프에 빠질 수 있다. 
//  그러므로 함수의 어떠한 상태가 변경될때까지 계속 동일하게 참조하도록 하는 
//  useCallback을 이용

  const fetchMoviesHandler = useCallback( async () => {
    setIsloading(true);
    setError(null);
    try {
     const response = await fetch(
     "http://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key=f5eef3421c602c6cb7ea224104795888&targetDt=20231212"
     );
     if (!response.ok) {
      throw new Error("에러가 발생 했습니다.");
     }
     const data = await response.json();
     console.log(data);
     const transformedMovies = data.boxOfficeResult.dailyBoxOfficeList.map(
      (movieData) => {
       return {
        key: movieData.movieCd,
        moviName: movieData.movieNm,
        openDate: movieData.openDt,
        rank: movieData.rank,
       };
      }
     );  
     setMovies(transformedMovies);
    } catch (error) {
     setError(error.message);
    }
    setIsloading(false);
   },
// 이 예제코드에서는 의존성이라고 할만한 부분이 없다. ( 바뀔만한 내용이없다. )
   []
  );


// useEffect 의존성 배열에 fetchMoviesHandler를 써야하는 이유는?
// 이 예제에서는 필요가 없다.
// 왜냐하면 fetchMoviesHandler 함수가 외부 상태에서 변경될일이 없기때문에 
// 빈배열로 해줘도 충분하긴하다.
// 하지만 다른곳에서는 충분히 함수가 외부상태에서 바뀔수있는 코드가 충분히 있을만하므로 
// 빈배열로 두지않고 연습해보자.


useEffect(() => {
   fetchMoviesHandler();
  }, [fetchMoviesHandler]);

  let content = <p>Found no moives.</p>;

  if (movies.length >0) {
   content = <MoviesList movies={movies} />;
  }

  if (error) {
   content = <p>{error}</p>;
  }

  if (isLoading) {
   content = <p>Loading...</p>;
  }

  return (
   <React.Fragment>
    <section>
     <button onClick={fetchMoviesHandler}>Fetch Movies</button>
    </section>
    <section>{content}</section>
   </React.Fragment>
  );
}

export default App;

새로고침 할때마다 "Fetch Movies"버튼을 누르지않아도 바로 데이터를 들여오는것을 확인할 수 있다.

앱 실행시 데이터를 바로 들여오게 하는 방법은

useEffect를 사용하자. ( with useCallback )

15.8 : POST 요청 보내기

이번에는 GET 요청말고 POST를 통해 데이터를 양방향으로 통신해보자.
백엔드로 사용할 서버는 FireBase로 간단한 데이터베이스를 만들어서 사용이 가능하다.

Add Movie버튼으로 데이터에대한 "POST“ 요청을 보내서 데이터베이스에 저장을하고,
Fetch Moive버튼으로 데이터베이스에 저장되어있는 데이터를 "Get"요청을 하여 가져올 예정이다.

App.js

import React, { useState, useEffect, useCallback } from "react";

import MoviesList from "./components/MoviesList";
import AddMovie from "./components/AddMovie";
import "./App.css";

function App() {
  const [movies, setMovies] = useState([]);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);

// GET 통신부분
  const fetchMoviesHandler = useCallback(async () => {
   setIsLoading(true);
   setError(null);
   try {
    // firebase url을 기입하고 뒤의 moives 부분은 임의의 이름을 기입하면된다.
    const response = await fetch(
     "https://react-http-3c308-default-rtdb.firebaseio.com/movies.json"
    );
    if (!response.ok) {
     throw new Error("Something went wrong!");
    }

   
    const data = await response.json();

    // data는 Object => firebase에서 만든 객체를가지는 키로
    // 이는 중첩객체를 가진다.
    console.log(data);

    // 따로 배열을 초기화한다.
    const loadedMoives = [];

    // for.. in문은 객체에서 문자열로 키가 지정된 모든 열거 가능한 속성에 대해 반복한다.
    for (const key in data) {
     loadedMoives.push({
      id: key,
      // 중첩객체를 통해 값을 읽고,
      // 따로 만든 배열에 값을 저장한다. 
      title: data[key].title,
      openingText : data[key].openingText,
      releaseDate: data[key].releaseDate, 
     })
    }

    setMovies(loadedMoives);
   } catch (error) {
    setError(error.message);
   }
   setIsLoading(false);
  }, []);

  useEffect(() => {
   fetchMoviesHandler();
  }, [fetchMoviesHandler]);

// POST 통신 부분   
  async function addMovieHandler(movie) {
    // fetch는 GET 요청 뿐만아니라 POST 요청도 가능하다.
    // 단 POST 요청시에 두번째 인자의 기입이 필요하다.
   const response = await fetch(
    "https://react-http-3c308-default-rtdb.firebaseio.com/movies.json",
    {
     method: "POST",
    // 백엔드와 프론트간의 데이터교환의 사용되는 유형인 json으로 바꿔야함
     body: JSON.stringify(movie),
    // 어떤 컨텐츠가 전달되는지 명확하게 알려주기위해 header를 사용한다.
     headers: {
      "Content-Type": "application/json",
     },
    }
   );
   const data = await response.json();
   console.log(data);
  }

  let content = <p>Found no movies.</p>;

  if (movies.length >0) {
   content = <MoviesList movies={movies} />;
  }

  if (error) {
   content = <p>{error}</p>;
  }

  if (isLoading) {
   content = <p>Loading...</p>;
  }

  return (
   <React.Fragment>
    <section>
     <AddMovie onAddMovie={addMovieHandler} />
    </section>
    <section>
     <button onClick={fetchMoviesHandler}>Fetch Movies</button>
    </section>
    <section>{content}</section>
   </React.Fragment>
  );
}
export default App;

중첩 객체를 for.. in문으로 다루는 코드를 다시한번 보자.

 const loadedMoives = [];
    for (const key in data) {
     loadedMoives.push({
      id: key,
      title: data[key].title,
      openingText : data[key].openingText,
      releaseDate: data[key].releaseDate, 
     })
    }

해당 중첩 객체를 콘솔창에 띄워보자.

console.log(data);

이런식으로 문자열을 키로, 값을 객체로 갖는 중첩 객체이다.

Object {
  "N1~ZD" : {
    ~~
    ~~
}

for..in문을 사용할때 문법은
in 뒤에는 객체를 서술하고,
for과 in 사이에는 in 뒤에 서술한 객체의 key값 참조를 할수 있어서 임의의 변수명을 지정하여 사용하면 된다.
for 객체의 key값 in 객체 => ex) (for keyy in data) { .. }

profile
Developer

0개의 댓글