이전시간까지는 데이터를 보내고 그 데이터를 백엔드에서 저장하는것까지 진행했었다.
이전까지의 내용을 간단히 풀어서 설명하면 이렇다.
- 백엔드를 만들고 서버를 구동시킨다.
- 프론트에서 업데이트된 데이터를 저장한다.
- 업데이트 된 시점에서 백엔드로 데이터를 보낸다.
- 백엔드에서 데이터를 받아서 JSON 파일에 저장한다.
그래서 이번엔 페이지를 다시 로드하거나 페이지를 처음 방문했을 때 백엔드에서 가져온 포스트가 표시되게 해볼 것이다.
✍ PostList.jsx - (front project)
/*import { useState } from "react";
import Post from "./Post";
import NewPost from "./NewPost";
import Modal from "./Modal";
import classes from "./PostList.module.css"; */
function PostList({ isPosting, onStopPosting }) {
const [posts, setPosts] = useState([]);
function addPostHandler(postData) {
fetch("http://localhost:8080/posts")
.then((response) => response.json())
.then((data) => {
setPosts(data.posts);
});
setPosts((existingPosts) => [postData, ...existingPosts]);
/* fetch("http://localhost:8080/posts", {
method: "POST",
body: JSON.stringify(postData),
headers: {
"Content-Type": "application/json",
},
});
}
// ========== 중략 ==========
*/
페이지를 다시 로드하거나 페이지를 처음 방문했을 때 아무것도 없는 데이터를 가져오기 위해서 백엔드에 get 요청
을 보내야한다. 실제로 하려는것은 get 요청
에 대한 응답을 받아 'posts'를 업데이트하는 것이다. 사용자가 아무것도 포스팅 하지 않았다면 빈 posts
데이터를 보내줄 것이니까.
이런 건 보통 async-await
를 사용해 처리한다. 데이터를 읽고 불러들이는 작업은 시간이 소요되는 작업이 될때가 많기 때문이다. 하지만 이 코드에선 사용할 수 없다. 왜냐하면 컴포넌트 함수는 반드시 JSX 코드나 다른 값을 반환해야 하고, 프로미스(Promise)를 반환하면 안 되기 때문이다. 그런데 함수 앞에 async
를 붙이면 모든 값을 프로미스 객체로 반환하기 떄문에 이 방법이 아니라 then
을 써서 응답을 처리한다.
순서는 아래 4단계로.
- 응답을 받고
.then()
response.json()
을 호출해 응답으로 부터 데이터를 압축 해제하고,- 응답에서 서버가 돌려보면 데이터만 추출한다.
- 서버가 돌려보낸 데이터는 객체 형태로 'posts'라는 필드에 가져온 포스트를 모두 담고 있고 그 데이터를 사용한다.
.then(data =>{data.posts})
or
.then(data =>{setPots(data.posts)})
이렇게
오류발생 : 이렇게 작성하면 무한루프가 되는 문제가 생긴다.
상태가 업데이트 되면 해당 component function이 다시 실행되고 그러면 실행되자마자 또 get으로 백엔드에서 데이터를 받아올거고, 다시 이 Fetch 요청도 다시 전송될 거고 그럼 또 요청을 보내고, 데이터를 받아 상태를 갱신하고 같은 동작을 반복하게 된다.
그러면 어떻게 해야할까? 상태가 업데이트 될 때 마다 함수를 실행하는 것이 아니라 데이터를 가져올때만 함수를 실행 되도록 하면 된다. 그렇게 하기 위해선 useEffect()
를 써야한다.
답 : 다른 Hooks을 쓰면 된다. ===
useEffect()
왜냐하면 useEffect안에 쓴 코드는 컴포넌트 함수가 실행될 때마다 다른 코드들처럼 매번 실행되지 않는다.
내 목적은 무한루프의 문제를 해결하는것이었고, 그것은 useEffect() 라는 훅으로 해결이 가능하다. 그리고 이건 상태 관리 이외에 사용한다. 즉 부수효과를 다룰때 사용되곤 한다.
부수효과란 상태 관리 이외에도 컴포넌트 내에서 발생하는 작업을 말한다. 예를 들어, 데이터 가져오기, 브라우저에게 알리기, 타이머 설정 등의 작업이 부수 효과에 해당함.
부수효과를 처리하는 것이 바로useEffect()
라는 훅이다. 여기서 useEffect()의 두번째 인자에 따라서 함수를 원하는 대로 실행 할 수 있다. 예를 들어 컴포넌트가 렌더링될 때마다 실행되게 할 수도 있고 단 한 번만 실행할 수도 있다.
이를 이용하여 부수 효과를 처리할 수 있다. useEffect()의 콜백 함수에는 컴포넌트가 렌더링될 때마다 실행되는 코드를 작성하면된다.
예를 들어, useEffect()를 사용하여 데이터를 가져오는 경우, 해당 코드를 useEffect() 콜백 함수 내부에 작성한다. 이렇게 하면 설정한 것에 따라서 컴포넌트가 렌더링될 때마다 데이터를 가져오는 것이 아니라, 컴포넌트가 마운트되거나 데이터가 변경될 때만 데이터를 가져올 수 있다. 이렇게 하면 애플리케이션의 성능을 최적화할 수 있다.
특징
- useState와 달리 useEffect는 값을 반환하지 않는다.
- 첫번째 인자로 함수를 값으로 받는다.
- 두 번째 인자로 배열을 받는다.
- async/await는 작업이 완료될 때까지 useEffect()를 차단할 수 있다.
<
그러니까 데이터를 가져오는 작업이 끝나기전이거나, 타이머 설정이 아직 끝나기 전이면(비동기 함수에서 데이터를 가져와서 화면에 렌더링하는 컴포넌트) 다른 컴포넌트가 렌더링이 되지 않는다. 즉, 콜백함수가 끝나기전에는 다른 작업들이 일시중단된다는 말이다.
첫번쨰 인자로 들어오는 이 함수는 리액트에 의해 실행되는데 이 함수가 실행돼야 한다고 판단되면 알아서 실행한다.
이 조건은 두번째 인자인 배열과 관련이 있는데 , 그건 밑에서 추가로 설명하겠다.
✍ 4번 추가 설명
리액트에서는 JavaScript의 비동기 함수들이 완료되기 전까지 다른 컴포넌트들이 렌더링 되지 않는 것은 아닙니다. 하지만, 비동기 함수의 결과가 아직 화면에 표시되지 않은 상태에서 렌더링이 발생할 경우, 예상치 못한 동작이 발생할 수 있습니다.
예를 들어, 비동기 함수에서 데이터를 가져와서 화면에 렌더링하는 컴포넌트가 있다고 가정해보겠습니다. 만약 이 컴포넌트가 데이터가 로드되기 전에 렌더링이 되면, 데이터가 없는 상태로 렌더링이 됩니다. 이 때 사용자는 예상치 못한 결과를 볼 수 있습니다.
따라서 리액트에서는 이러한 문제를 방지하기 위해, 비동기 함수가 완료되기 전까지는 화면이 다시 렌더링 되지 않도록 설계되어 있습니다. 이를 통해 데이터가 로드되면, 리액트는 변경된 상태를 반영하여 화면을 다시 렌더링합니다.
✍ PostList.jsx - (front project)
/*import { useEffect, useState } from "react";
import Post from "./Post";
import NewPost from "./NewPost";
import Modal from "./Modal";
import classes from "./PostList.module.css";*/
function PostList({ isPosting, onStopPosting }) {
const [posts, setPosts] = useState([]);
useEffect(() => {
async function fetchPosts() {
const response = await fetch("http://localhost:8080/posts");
const resData = await response.json();
setPosts(resData.posts);
}
}, []);
function addPostHandler(postData) {
fetch("http://localhost:8080/posts", {
method: "POST",
body: JSON.stringify(postData),
headers: {
"Content-Type": "application/json",
},
});
setPosts((existingPosts) => [postData, ...existingPosts]);
}
/*return (
<>
{isPosting && (
<Modal onClose={onStopPosting}>
<NewPost onCancel={onStopPosting} onAddPost={addPostHandler} />
</Modal>
)}
<ul className={classes.posts}>
{posts.length > 0 &&
posts.map((post) => (
<Post key={post.body} author={post.author} body={post.body} />
))}
</ul>
{posts.length === 0 && (
<div style={{ textAlign: "center", color: "white" }}>
<h2>포스트가 없습니다.</h2>
<p>여기에 내용을 추가해보세요 !</p>
</div>
)}
</>
);
}
export default PostList;*/
useEffect로 문제 해결했다. 설명하자면, useEffect 함수 안에 별도의 함수를 만들고, useEffect 함수 자체를 비동기 함수로 바꾸지 않았다. 왜냐하면 useEffect가 인자로 받는 함수에서는 프로미스를 반환하면 안 되니까.
그래서 안에 중첩 함수를 만들어 효과 함수 안에서 바로 실행할 수 있게 만들었다. 이렇게 하면 효과 함수에서 async-await를 쓸 수 있다.
여기까지 작성한 코드를 저장 한다음에 새로운 포스트들을 입력한 후에 새로고침을 눌러보면 데이터가 그대로 남아있어서 포스트 리스트들이 사라지지 않은 것을 확인 할 수 있다!! 드디어 백엔드랑 연동 성공!!!! 🎉🎉🎉🎉🎉🙂🎉🎉🎉🎉
물론 이것들은 백엔드에서 가져온 데이터들이다.
posts.json
파일에 저장된 것들을.
방금 나는 useEffect를 사용해 무한 루프 문제를 해결했다. 이 훅은 이용하면 리액트가 컴포넌트를 다시 실행할떄마다 똑같이 실행되지 않기 떄문이다. 그럼 useEffect는 언제 실행되는걸까?
그건 두번쨰 인자인 배열로 결정된다. 이 배열에는 효과 함수를 실행할 의존성을 정의한다.의존성에는 변수든, 함수든 useEffect 함수 바깥에 정의된 거라면 뭐든 쓸 수 있다. 즉, 리액트 컴포넌트에 있는 거면 이 컴포넌트에 있든 props를 통해 받은 부모 컴포넌트에 있든 상관 없다. 이 useEffect 함수 밖에 있는 그 변수 또는 함수가 변경될 때마다 변경된 값을 받아서 이 useEffect 함수를 다시 실행한다.
여기에 빈 배열을 넣으면 이 함수에는 의존성이 없다는 뜻이기 때문에 이 효과 함수는 두 번 실행되지 않는다.
리액트는 이 함수를 단 한 번 실행하는데 바로 이 컴포넌트가 처음 렌더링될 떄임. 정확히 말하면, 컴포넌트가 처음 렌더링된 직후에 실행된다.
순서대로 보면, 처음에 포스트 없이 컴포넌트가 렌더링되고 그 직후에 useEffect 함수가 실행되면서 포스트가 업데이트된다. 근데 그 과정이 너무 빨라서 사용자는 이렇게 포스트를 가져온 뒤의 상황만 볼 수 있는것임.
다시 한번 여기까지 한 내용은
1. useEffect 함수를 써서 HTTP 요청 전송을 처리했다.
2. 컴포넌트가 처음 렌더링될 때 데이터를 가져오는 방법