앱으로 직접 데이터를 가져오거나 저장하고, 연결을 맺는 행위는 절대 해서는 안 된다.
만약에 클라이언트 내부에서 DB에 직접 연결을 하게 된다면 또는 브라우저의 자바스크립트 코드를 통해 DB의 인증 정보를 노출시키는 행위이기 때문이다.
잊지 말아야 할 것이, 브라우저에서 실행되는 모든 자바스크립트 코드는 브라우저뿐만이 아니라
웹 사이트의 사용자들도 접근할 수 있다는 것이다.
JS를 사용하여 어떤 HTTP 요청이든 전달할 수 있다.
예를 들어 패키지 중 axios라는 것도 있다. 어떤 자바스크립트 라이브러리를 사용하는가에 관계없이 HTTP 요청 전송을 하고 이에 대한 반응을 매우 간단하게 할 수 있는 패키지이다. 심지어 라이브러리 없이도 사용할 수 있다
하지만 요즘은 Fetch API를 활용하는 브라우저 내장형이며 데이터를 불러오고 이름과는 다르게 데이터 전송도 가능하다. 이 API를 통해 HTTP 요청을 전송하고 응답을 처리할 수 있다.
const 함수Trigger = () =>{
fetch("URL이름").then(res=>{
return res.json();
}).then(data=>{
const 담고싶은_변수명 = data.results.map(아무거나=>{
return{
key1:아무거나.value1,
key2:아무거나.value2,
key3:아무거나.value3,
key4:아무거나.value4
}
})
setMovies(담고싶은_변수명);
});
}
첫번째 인수는 요청을 전송하려는 URL의 문자열이다.
그리고 두 번째 인자는 JS 객체이다. 이 인자를 통해 다양한 선택사항을 지정할 수 있고 추가적인 헤더나 바디 또는 HTTP 요청 메소드의 변경 등을 할 수 있다.
이 함수의 return값은 Promise 객체를 반환하는데 이 객체는 우리가 잠재적으로 발생할 수 있는 오류나 호출에 대한 응답에 반응할 수 있게 해준다
그래서 .then()
메서드를 사용하여 비동기로 응답이 완료된 후 호출하여 .catch()
로 오류를 처리하면 된다.
응답 시 JSON으로 주고받는데 그 이유가 JSON은 데이터 교환에 사용하는 간단하지만 매우 유명한 형식이기 때문이다.
그리고 JSON 데이터의 또 다른 이점은 자바스크립트로의 변환 작업이 필요하지만 그럼에도 불구하고 파일에서 자바스크립트 객체로의 변환이 매우 쉽다는 것이다 다행히 이 response(res) 객체에는 내장 메소드 .json()
이 있어서 JSON response의 본문을 코드에서 사용할 수 있는 자바스크립트 객체로 자동 변환해준다.
마지막으로 data가 바뀌었고 이것을 state로 관리하여 사용자 UI에 다시 랜더링해야하기 때문에 마지막에 setState로 관리하는 모습이다.
.then()
chain으로 처리할 수 있지만 await과 async로도 처리할 수 있다.async function 함수Trigger = {
const res = await fetch("URL이름");
const data = await res.json();
const 담고싶은_변수명 = data.results.map(아무거나=>{
return{
key1:아무거나.value1,
key2:아무거나.value2,
key3:아무거나.value3,
key4:아무거나.value4
}
});
setMovies(담고싶은_변수명);
};
.then()
으로 바뀌어 들어간다.우리가 Http 전송을 하는 경우에도 무언가 잘못된 것이 있다면 오류가 발생할 수 있다.
예를 들어 네트워크 연결이 없다던가 또는 오류 응답코드(http status)를 넘겨받는 오류들 말이다.
어떤 백엔드 어플리케이션과 통신하든 간에 서로 다른 state들을 맞닥뜨릴 수 있으므로 처리 방법을 아는 것은 중요하다. request가 error를 받을 수도 있고 data를 받을 수도 있는데 이 data가 비어있을 수도 있다. 따라서 이런 여러 가지의 시나리오를 다룰 줄 알아야 한다.
const [error, setError] = useState(null);
async function fetchMovieTrigger() {
setIsLoading(true);
setError(null); // <= 여기 null을 한 번 더 초기화하는 이유는 전에있던 error를 없애기 위함이다.
try {
const response = await fetch("https://swapi.dev/api/films");
console.log(response.status);
if (!response.ok) {
throw new Error("뭔가 심상치 않은데?!");
}
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);
setIsLoading(false);
} catch (err) {
setError(err.message);
}
setIsLoading(false);
};
let content = <p>영화가 없습니다.</p>;
if (movies.length>0) {content = <MoviesList movies={movies}/>}
if (error) {content = <p>{error}</p>}
if (isLoading) {content = <p>로딩중...</p>}
이게 가능한 이유는 HTTP 요청 전송은 일종의 sideEffct로 component의 state를 바꿔버리기 때문이다.
이때 useEffect함수를 메인 컴포넌트의 함수 일부분으로 호출하지만 않도록 신경써주어야한다.
앞서 배운것 처럼 만약 그렇게 하면 함수 호출이 되는 순간 state의 갱신이 발생하고 컴포넌트 함수가 재 렌더링, 재평가되면서 함수가 다시 호출되는 무한 루프가 발생하기 때문이다.
위 사진 처럼 Trigger안에 Trigger가 들어가면 Trigger가 최신화 되었으니 의존성에 의하여 Trigger를 호출하고 Trigger가 호출되었으니 또 useEffect가 돌아고....
그래서 의존성안에 아무것도 안 써넣는 것도 하나의 쉬운 방법이지만 이는
"함수가 외부 state를 사용하게 되는데"
이러면 의도치 않는 버그가 발생할 수 있기 때문에 가장 좋은 방법은 useCallback hook를 사용하는 것이다.
const fetchMovieTrigger = useCallback(async() => {
// 초기화 구문들
setIsLoading(true);
...
try {
~~반환 로직~~
// state 설정 구문들
setMovies(transformedMovies);
...
} catch (err) {
setError(err.message);
}
setIsLoading(false);
}, []);
useEffect(()=>{
fetchMovieTrigger();
}, [fetchMovieTrigger]);