react정리8
리엑트 앱은 일반적으로 해당 백엔드서버 또는 백엔드API로 불리는 서로 다른 URL로의 요청을 전송하는 서버와
통신하게 된다 이제 인증 정보는 백엔드 앱에 저장되고 다른 서버에 있으므로 사용자가 이 코드를 볼 수 없다
백엔드 앱과의 통신은 보안에 관련된 세부 사항이 필요 없으므로 데이터베이스와 안전하게 통신을 주고받을 수 있다
영화가 있는 데이터베이스로부터 데이터를 가져오기
swapi.dev 사이트에서 사용
API-Application Programming Interface
API는 매우 넓은 개념으로 단순히 리엑트나 HTTP요청만 있는 것이 아니다
코드를 통해 명확하게 정의된 인터페이스를 다루며 또 어떤 결과를 얻기 위한, 작업에 대한 규칙이 명확하게 정의된 것을 다루고 있다는 뜻
HTTP 요청에 대한 API를 말할 때는 보통 REST, 또는 GraphQL API를 말하게 된다
이 두 개는 서버가 데이터를 노출하는 방식에 대한 서로 다른 표준
REST API는 URL같은 곳에 요청을 전송시 특정한 형식에 맞춰 데이터를 전달해준다
서로 다른 URL에 각각 다른 요청을 보내게 되면 그에 맞는 서로 다른 데이터들을 제공
접근 위치가 다르면 결과도 다르다
리엑트 앱에서 요청을 전송
axious - github에서 받을 수 있고, 어떤 자바스크립트 라이브러리를 사용하는가에 관계없이 HTTP전송을 하고
이에 대한 반응을 매우 간단하게 할 수 있는 패키지 라이브러리 없이도 사용할 수 있다
fetch - 자바스크립트 내에서 HTTP요청을 전송하는 내장 매커니즘
Fetch API는 브라우저 내장형이며 데이터를 불러오고, 데이터 전송도 가능하다
이 API를 통해 HTTP요청을 전송하고 응답을 처리할 수 있다
import React, { useState } from "react";
import MoviesList from "./components/MoviesList";
import "./App.css";
function App() {
//api결과 상태값 저장하기 위한 useState
const [movies, setMovies] = useState([]);
//영화 정보를 불러오기 위해 fetch api사용
function fetchMoviesHandler() {
//불러올 주소(요청을 전송하려는 url전달),
//두번째 인자에 다양한 선택사항을 지정한다
//then()추가 catch()는 잠재적 오류를 처리할 수 있다
//them괄호안에 response응답을 받은 뒤 arrow function 으로 처리
//응답을 받은 뒤 이 함수에서 응답을 사용할 수 있다
//함수 안에 들어오는 것은 객체로 응답에 대한 많은 데이터를 갖고 있다
//API는 데이터를 JSON형식으로 전송
//response.json() json사용
//json reponse본문을 자바스크립트 객체로 변환해준다
//response.json 메소드 역시 프로미스 객체를 반환하므로
//우리가 해야할 것은 프로미스를 반환하고
//추가적인 them()구역을 생성해야한다
//이렇게 하면 이 데이터 변환 작업이 끝날 대 작동하게된다
//두번재 then에선 아까 봤던 json구조를 그대로 가진다
//data.results에 접근해서 이 결과를 상태에 저장하는 것
fetch("https://swapi.dev/api/films/")
.then((response) => {
return response.json();
})
//setMovies를 호출해서(상태를 저장할) data
.then((data) => {
const transfomredMovies = data.results.map((movieData) => {
//episode_id, title 등 은 가져온 객체의 이름
return {
id: movieData.episode_id,
title: movieData.title,
openingText: movieData.opening_crawl,
releaseDate: movieData.release_date,
};
});
setMovies(transfomredMovies);
});
}
//버튼에 위의 함수 연결
return (
<React.Fragment>
<section>
<button onClick={fetchMoviesHandler}>Fetch Movies</button>
</section>
<section>
<MoviesList movies={movies} />
</section>
</React.Fragment>
);
}
export default App;
asynv await 사용
async function fetchMoviesHandler() {
const response = await fetch("https://swapi.dev/api/films/");
const data = await response.json();
const transfomredMovies = data.results.map((movieData) => {
return {
id: movieData.episode_id,
title: movieData.title,
openingText: movieData.opening_crawl,
releaseDate: movieData.release_date,
};
});
setMovies(transfomredMovies);
}
로딩 데이터 및 state처리
import React, { useState } from "react";
import MoviesList from "./components/MoviesList";
import "./App.css";
function App() {
const [movies, setMovies] = useState([]);
//loading상태 설정 위해 state생성
const [isLoding, setIsLoading] = useState(false);
async function fetchMoviesHandler() {
//함수가 실행되기 시작할때 상태값 true로 변경
setIsLoading(true);
const response = await fetch("https://swapi.dev/api/films/");
const data = await response.json();
const transfomredMovies = data.results.map((movieData) => {
return {
id: movieData.episode_id,
title: movieData.title,
openingText: movieData.opening_crawl,
releaseDate: movieData.release_date,
};
});
setMovies(transfomredMovies);
//데이터로딩이 모두 끝났을 때 상태값을 true로 설정
setIsLoading(false);
}
return (
<React.Fragment>
<section>
<button onClick={fetchMoviesHandler}>Fetch Movies</button>
</section>
<section>
{/* 로딩 중이 아니고 movies가 0이상일 경우(1개이상 있을 경우), MoviesList출력 */}
{!isLoding && movies.length > 0 && <MoviesList movies={movies} />}
{/* 로딩 중이 아니고 영화가 0일 경우 */}
{!isLoding && movies.length === 0 && <p>Found no movies.</p>}
{/* 로딩중일 경우 텍스트 출력 */}
{isLoding && <p>Loading...</p>}
</section>
</React.Fragment>
);
}
export default App;
HTTP 오류 처리
import React, { useState } from "react";
import MoviesList from "./components/MoviesList";
import "./App.css";
function App() {
const [movies, setMovies] = useState([]);
const [isLoding, setIsLoading] = useState(false);
//오류 처리를 위한 상태 설정 초기에는 오류가 없기 때문에 null과 같은 상태
const [error, setError] = useState(null);
async function fetchMoviesHandler() {
setIsLoading(true);
//함수가 실행되고 이전에 받았을 수도 있는 오류상태를 초기화
setError(null);
//ayanc await 사용하지 않았을 경우 cache()를 추가해 오류 확인
//ayanc await사용하기 때문에 try-catch를 사용해야 한다
//try에서 발생할 수 있는 잠재적 오류를 catch로 포착한다
//fetch api는 오류를 받아도 기술적인 오류로서 처리하지 않는다
try {
const response = await fetch("https://swapi.dev/api/films/");
const data = await response.json();
//응답이 ok가 아닌지 확인하고 자체적인 오류생성
if (!response.ok) {
throw new Error("Something went wrong!");
}
const transfomredMovies = data.results.map((movieData) => {
return {
id: movieData.episode_id,
title: movieData.title,
openingText: movieData.opening_crawl,
releaseDate: movieData.release_date,
};
});
setMovies(transfomredMovies);
} catch (error) {
// 여기서 error가 설정한 오류 메세지 string
setError(error.message);
}
setIsLoading(false);
}
let content = <p>Found no movies.</p>;
if (movies.length > 0) {
content = <MoviesList movies={movies} />;
}
if (error) {
content = <p>{error}</p>;
}
if (isLoding) {
content = <p>Loading...</p>;
}
return (
<React.Fragment>
<section>
<button onClick={fetchMoviesHandler}>Fetch Movies</button>
</section>
<section>
{content}
</section>
</React.Fragment>
);
}
export default App;
요청에 useEffect사용
import React, { useEffect, useState, useCallback } from "react";
import MoviesList from "./components/MoviesList";
import "./App.css";
function App() {
const [movies, setMovies] = useState([]);
const [isLoding, setIsLoading] = useState(false);
const [error, setError] = useState(null);
//함수 객체 오류를 피하기 위해 useCallback사용
const fetchMoviesHandler = useCallback(async () => {
setIsLoading(true);
setError(null);
try {
const response = await fetch("https://swapi.dev/api/films/");
if (!response.ok) {
throw new Error("Something went wrong!");
}
const data = await response.json();
const transfomredMovies = data.results.map((movieData) => {
return {
id: movieData.episode_id,
title: movieData.title,
openingText: movieData.opening_crawl,
releaseDate: movieData.release_date,
};
});
setMovies(transfomredMovies);
} catch (error) {
setError(error.message);
}
setIsLoading(false);
}, []);
//버튼 클릭하기 전에 특정 컴포넌트가 로딩되자 마자 데이터를 가져오기 위해 useEffect 사용
//데이터를 즉시 fetch
useEffect(() => {
fetchMoviesHandler();
}, [fetchMoviesHandler]);
let content = <p>Found no movies.</p>;
if (movies.length > 0) {
content = <MoviesList movies={movies} />;
}
if (error) {
content = <p>{error}</p>;
}
if (isLoding) {
content = <p>Loading...</p>;
}
return (
<React.Fragment>
<section>
<button onClick={fetchMoviesHandler}>Fetch Movies</button>
</section>
<section>
{content}
</section>
</React.Fragment>
);
}
export default App;
Firebase사용
구글 계정 로그인
코드 작성 없이 사용 가능한 백엔드
요청을 주고받을 수 있는 완전한 rest api를 제공하는 풀 백엔드 어플리케이션
데이터를 가져오고 저장
더미 백엔드를 이용해 서버 기반 코드 없이 무료 실습 가능
로그인 후 프로젝트 만들기
realtime-database 클릭 - start in test mode
만들어진 url을 통해 firebase 백엔드로 데이터를 보낼 수 있다
App.js에서 스타워즈 url을 friebase url로 변경해주고 뒤에 movies.json 추가하면 데이터베이스에
새로운 노드가 만들어진다 이것은 동적 api로 서로 다른 세그먼트 사용해 데이터베이스의 서로 다른 노드들에 데이터를
저장할 수 있게 설정해준다 요청을 전달하려는 url끝에 .json을 추가해야 한다
const response = await fetch('https://react-http-79026-default-rtdb.firebaseio.com/movies.json');
POST 요청 보내기
Add Movie버튼 눌러서 POST요청을 전송하고 Firebase에 저장하기
fetch는 기본적으로 get요청을 보내기 때문에 fetch API에 2번째 인자를 전달한다 method : 'POST'
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);
const fetchMoviesHandler = useCallback(async () => {
setIsLoading(true);
setError(null);
try {
const response = await fetch(
"https://react-http-79026-default-rtdb.firebaseio.com/movies.json"
);
if (!response.ok) {
throw new Error("Something went wrong!");
}
const data = await response.json();
const transformedMovies = data.results.map((movieData) => {
return {
id: movieData.episode_id,
title: movieData.title,
openingText: movieData.opening_crawl,
releaseDate: movieData.release_date,
};
});
setMovies(transformedMovies);
} catch (error) {
setError(error.message);
}
setIsLoading(false);
}, []);
useEffect(() => {
fetchMoviesHandler();
}, [fetchMoviesHandler]);
async function addMovieHandler(movie) {
const response = await fetch(
"https://react-http-79026-default-rtdb.firebaseio.com/movies.json",
{
//fetch의 기본 method는 GET이기 때문에 두번째 인자로 POST 전달
//firebase에서는 post요청을 보내면 리소스를 만들어준다
//이제 저장해야하는 리소스 만들기 위해 body라는 옵션 추가
//body는 자바스크립트 객체가 아닌 JSON 데이터를 필요로 하기 때문에
//자바스크립트 객체를 JSON으로 바꾸기 위해 JSON객체 사용한 뒤에 stringify 호출해준다 이러면 자바스크립트 객체나 배열을 JSON형식으로 바꿔주게 된다
//headers 키를 추가하고 값으로 객체를 지정한다 Firebase에는 이 헤더가 필요하지 않지만
//요청을 받는 대다수의 API들은 이러한 헤더를 필요로 한다 이 헤더를 통해 어떤 컨텐츠가 전달되는지 알 수 있다
//이 데이터를 가지고 URL에 POST 요청을 보낼 수 있다
//이 작업은 비동기 작업이고 promise를 돌려받을 것이므로 앞에 async, await를 적어준다
method: "POST",
body: JSON.stringify(movie),
headers: {
"Content-Type": "application/json",
},
}
);
//그 다음 await response.json을 통해 데이터를 가져온다
const data = await response.json();
}
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;
새로고침 후 영화 제목과 text 날짜를 입력하고 add Movie버튼을 누르면
firebase에 데이터항목에서 movies라는 새로운 노드가 생성되고 이 노드 안에 암호화된 id가 있고 그 안에 전송된 테이터가 나온다
리엑트 앱에서 개발자 도구를 열어 자바스크립트 콘솔을 보면 firebase로부터 받은 response객체를 볼 수 있다
이제 Fetch Movies버튼 누르면 영화 목록을 가져와야 한다
영화 데이터를 배열이 아닌 객체로 받았기 때문에 ID가 key이고 실제 데이터는 중첩 객체
이를 변환해서 영화로 표시한다
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);
const fetchMoviesHandler = useCallback(async () => {
setIsLoading(true);
setError(null);
try {
const response = await fetch(
"https://react-http-79026-default-rtdb.firebaseio.com/movies.json"
);
if (!response.ok) {
throw new Error("Something went wrong!");
}
const data = await response.json();
const loadeMovies = [];
for (const key in data) {
loadeMovies.push({
id:key,
title: data[key].title,
openingText: data[key].openingText,
releaseDate: data[key].releaseDate
});
}
setMovies(loadeMovies);
} catch (error) {
setError(error.message);
}
setIsLoading(false);
}, []);
useEffect(() => {
fetchMoviesHandler();
}, [fetchMoviesHandler]);
async function addMovieHandler(movie) {
const response = await fetch(
"https://react-http-79026-default-rtdb.firebaseio.com/movies.json",
{
method: "POST",
body: JSON.stringify(movie),
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;