Ayden's journal

React 데이터 가져오기

useEffect

useEffect를 호출하면 리액트는 정해진 콜백 함수를 등록하고, useEffect의 두 번째 파라미터인 'dependency list'의 내용도 기억한다.

랜더링이 끝나면 등록된 콜백 함수를 실행하며, 이로 인해 다시 랜더링하는 과정에서 또다시 useEffect가 호출된다. 이 때 리액트는 이전에 기억한 dependency list 값과 콜백 실행 이후의 dependency list 값을 비교하는데, 이 값이 동일하다면 useEffect는 콜백을 등록하지 않는다.

const handleLoad = async () => {
const { foods } = await getFoods();
setItems(foods);
};
useEffect(() => {
handleLoad();
}, []);

위 예시의 경우 처음 랜더링 될 때 호출된 useEffect가 handleLoad() 콜백을 등록하고, 랜더링이 끝나면 handleLoad() 콜백이 동작하여 setItems가 foods state를 변경시킨다. 이로 인해 또 다시 랜더링이 되지만, 이전과 이후 랜더링에서의 dependency list 값이 모두 빈 배열로 동일하기 때문에 추가적인 랜더링은 없다.

 

useAsync

React에서는 비동기 작업을 처리하고 관리하기 위해 useAsync라고 하는 이름의 커스텀 훅을 만들어 사용하곤 한다. 이 훅은 주로 API 호출, 데이터 가져오기, 파일 로딩 등과 같은 비동기 작업을 처리하는 데 도움이 된다. useAsync는 정해진 형태는 없지만, 일반적으로는 아래와 같은 몇 가지 특정한 기능을 제공해야 한다.

  1. 상태 관리: useAsync는 작업의 상태를 관리하는 데 사용됩니다. 이는 대개 "대기 중", "로딩 중", "성공", "실패"와 같은 상태를 추적하여 해당 작업의 현재 진행 상황을 파악하는 데 도움이 됩니다.
  2. 비동기 작업 수행: 주로 비동기 작업(예: 데이터 가져오기, API 호출 등)을 수행합니다. 이 훅은 비동기 작업을 시작하고 관리하는 데 필요한 로직을 제공합니다.
  3. 에러 핸들링: 비동기 작업 중 발생하는 오류를 처리하고 관리합니다. 이를 통해 오류 메시지를 보여주거나 적절한 조치를 취할 수 있습니다.
  4. 캐시 및 재사용: 결과를 캐시하여 이전 결과를 다시 사용하거나, 새로운 요청을 보내기 전에 이전 결과를 반환하는 등의 작업을 수행합니다. 이는 불필요한 중복 요청을 방지하고 성능을 향상시킬 수 있습니다.

 

비동기 state 변경

비동기 작업을 통해 state를 변경하려고 한다. 이때, 동기 작업으로 인해 state가 변경되어도 비동기 작업에서 물고 있는 setter 함수는 변경 전의 state를 참조하게 된다. 따라서 동기 비동기 상관 없이 가장 최신의 state를 반영하고 싶다면 setter 함수에 콜백 함수를 물려주면 된다.

// setter의 콜백 함수 첫번째 파라미터에는 최신 state가 들어간다
setItems((prevItems) => [...prevItems, ...reviews]);

 

 

페이지네이션

인스타그램에 접속할 때, 필요한 모든 데이터를 한 번에 넘겨받으려면 리스폰스의 크기는 커지고 서버가 터지고 내 속도 터질 것이다. 때문에  여러 개의 컨텐츠를 일정한 개수로 끊어놓고 필요할 때마다 추가적으로 리스폰스를 보내게 되는데, 이처럼 전체 정보를 쪼개서 제공하는 방법을 페이지네이션pagination이라고 한다.

정해진 쿼리를 통해 서버에 요청하면 리스폰스에 paging이라고 하는 객체가 함께 내려온다. 이 객체는 전체 데이터 개수를 알려주는 count와 다음 데이터가 있는지 알려주는 hasNext로 구성되어있다.

 

오프셋과 커서

페이지네이션의 경우 서버에 요청하는 방식에 따라 크게 오프셋 기반과 커서 기반으로 나뉘게 되지만, 둘 다 limit를 통해 새로 받아올 데이터 분량을 정하게 된다. 가령 아래의 예시처럼 'offset=20&limit=10'라면, 이를 통해 새로 받는 리스폰스는 20번부터 29번 인덱스까지 딱 10개의 데이터를 포함하게 된다. 커서도 마찬가지로 서버가 지정한 WerZxc 데이터부터 10개를 보내줄 것이다.

const offset = 'https://example.com/posts?offset=20&limit=10'
const cursor = 'https://example.com/posts?cursor=WerZxc&limit=10'
  • 오프셋 : 지금까지 받아온 데이터의 개수 기준으로 동작. 때문에 도중에 서버의 데이터가 추가되거나 삭제되는 등 변화가 발생하면, 리스폰스로 받아오는 데이터가 (의도와는 다르게) 특정 데이터를 중복해서 가져오거나, 빼먹을 수 있다.
  • 커서 :  지금까지 받아온 데이터를 표시하는 책갈피처럼 동작. 중간에 서버의 데이터가 삭제되거나 해도 커서를 기준으로 동작하기 때문에 받아오는 데이터에 중복이나 제외되는 값이 없다.

이렇게만 놓고 보면 커서가 월등히 좋은 것 같지만, 커서는 서버가 복잡해진다는 문제가 있고, 따라서 도중에 별다른 데이터 변경이 없다면 오프셋만으로도 충분하다.

 

오프셋 구현

const [items, setItems] = useState();
const [offset, setOffset] = useState(0);
const [hasNext, setHasNext] = useState(true);
const [order, setOrder] = useState('createdAt');
const LIMIT = 6;
  • items : 지금까지 받아온 item 배열
  • offset : 지금까지 받아온 item 수
  • hasNext : 더 받아올 페이지가 있는지 없는지를 확인
  • order : 서버에서 어떤 order로 페이지를 받아올 것인지
  • LIMIT = 한 번에 받아올 item 수
const handleLoad = async ({ order, offset, LIMIT }) => {
const { paging, reviews } = await getReviews({ order, offset, LIMIT });
if (offset === 0) {
setItems(reviews);
} else {
setItems([...items, ...reviews]);
}
setOffset(offset + LIMIT);
setHasNext(paging.hasNext);
};
useEffect(() => {
handleLoad({ order, offset: 0, LIMIT });
}, [order]);
<button disabled={!hasNext} onClick={handleLoad}>불러오기</button>

 

블로그의 프로필 사진

블로그의 정보

Ayden's journal

Beard Weard Ayden

활동하기