-
Notifications
You must be signed in to change notification settings - Fork 2
3주차기술공유
- 무한스크롤 == pagination
- offset, limit 을 이용한 pagination
- cursor based pagination
- data fetch 의 타이밍
- scroll과 history
- 결국은 사용성 fake loading , lazy loading?
- 궁금하지만 아직 해결하지 못한 부분
추가로 궁금 스크롤에서 캐싱은 왜하지? list에 대해 state관리를 어떻게 하는지
- fetch 해올 때 state가 변경된다면 컴포넌트가 전부 리렌더링 되지 않는지! ㄴ 오! 저도 궁금해요
일단 목차에 해당하는 내용에서 생각해본 내용이나 궁금한부분들을 적어주시면 좋을 것 같아요 . 해결책도 적어주시면 좋습니다. 적은 부분에 대해서 피드백도 좋고 추가로 작성해 주시는 것도 좋습니다.🙌🏼
- 무한스크롤에서도 캐싱을 해야할까? 왜?? - 이렇게!
그럼 취합해서 글을 만들겠습니다
무한 스크롤은 스크롤이 끝나지 않고 쭉 내려가는 UI입니다. 사용자가 봤을 때는 데이터가 계속 보여지는 것 같지만 데이터를 어떻게 로드할까를 생각해본다면 페이지의 개념이라고 생각 할 수 있습니다.
하지만 다음페이지 버튼을 눌렀을 때 다음 페이지에 해당하는 데이터를 로드해 오는 것처럼 생각한다면 페이지네이션과 같다고 생각할 수 있습니다.
pagination 을 구현하기 위해서는 database의 offset, limit의 개념을 사용할 수 있습니다. offset은 어느 index부터 데이터를 조회할 것인지 limit은 offset으로부터 어디까지의 데이터를 조회할 것인지를 의미합니다.
const OFFSET = 10;
const fetchMoreFeed = async () => {
const { data: value } = await fetchMore({
variables: {
first: OFFSET,
after: cursorIdx,
},
updateQuery: (prev, { fetchMoreResult }) => {
if (!fetchMoreResult) {
return prev;
} else {
const { feeds: feedItems } = fetchMoreResult;
setCursorIdx(OFFSET + cursorIdx);
}
...
}
});
...
};
- apollo useQuery Hook을 이용해서 데이터 요청
하지만 문제가 있습니다 내가 다음글을 보기 전에 새로운 글이 등록되거나, 삭제되었다면 DB에서는 이미 조회한 글이나 다다음 글을 조회해 올 수 있습니다.
저희가 진행하고 있는 facebook과 같이 사용자들의 글이 실시간으로 등록되고 보여져야 되는 경우도 이런 경우라고 생각했습니다. 그래서!
cusrsor based pagination
을 적용하기로 했습니다. 지금 보고있는 피드의 마지막 item id 이후의 데이터만 조회하는 것 입니다.
여기서 feed item의 id는 고유한 값이여야 합니다. 그래야 데이터베이스쪽에서 잘못된 값을 조회하지 않으니까요.
const [cursor, setCursor] = useState<string>("9999-12-31T09:29:26.050Z");
const OFFSET = 10;
const fetchMoreFeed = async () => {
const { data: value } = await fetchMore({
variables: {
first: OFFSET,
currentCursor: cursor
},
updateQuery: (prev, { fetchMoreResult }) => {
if (!fetchMoreResult) {
return prev;
} else {
const { feeds: feedItems } = fetchMoreResult;
const lastFeedItem = feedItems[feedItems.length - 1];
setCursor(lastFeedItem.createdAt);
}
return ...
}
});
- 마지막으로 feed item의 시간을 cursor로 결정하고 해당 피드 이후의 피드들에서 offset으로 가지고오기로 했습니다
- feed item의 등록 시간은 밀리초까지 저장되는 datetime type입니다.
그럼 데이터는 언제 불러서 언제 로드해야할까요? 무한 스크롤이므로 스크롤이 스크린의 바닥에 닿았을 때 새로운 데이터들을 로드해와야 합니다.
현재 저희는 react 와 graphql neo4j를 이용해서 개발을 진행하고 있습니다. 리엑트에서의 저희가 구현한 방법으로 예시를 들겠습니다.
- scroll 이벤트의 감지
- document height : 문서 전체의 높이
- window height : 화면의 높이.
- scroll top :스크롤의 top이 위치하고 있는 높이.
document.documentElement.scrollTop + document.documentElement.clientHeight === document.documentElement.scrollHeight
- scroll의 끝을 감지하는 hook
const useScrollEnd = () => {
const [state, setState] = useState(false);
const onScroll = () => {
if (
document.documentElement.scrollTop +
document.documentElement.clientHeight ===
document.documentElement.scrollHeight
) {
setState(true);
} else {
setState(false);
}
};
useEffect(() => {
window.addEventListener("scroll", onScroll);
// 스크롤 이벤트는 꼭 삭제해줍니다!
return () => window.removeEventListener("scroll", onScroll);
}, []);
return state;
};
- 해당 상황이 감지가되어서 상태값을 바꿀 때 마다 앞에서 구현한 fetchMoreFeed를 수행합니다.
이제 스크롤에 따라 계속 데이터가 계속 이어져서 보이는 것처럼 보입니다.하지만 만약 다른 페이지로 갔다가 뒤로가기를 누른다면 어떻게 될까요? 다른 게시글에 갔다가 돌아왔을 때 다시 맨 위부터 다시 보이는 것 보다 보고 있던 페이지 근처의 데이터가 보이는 것이 더 좋을 것 같습니다. 페이지를 패칭할 때
정의 : provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document's *viewport. => 비동기적으로 타겟 엘리먼트와, 해당 엘리먼트의 부모나 뷰포트상에서의 교차 지점을 감지하는 방법
*viewport? 컴퓨터 그래픽상에서 현재보여지는 화면 영역
비동기로 작업하기 때문에 스크롤이 더 자연스러움
let options = {
root: document.querySelector('#scrollArea'),
rootMargin: '0px',
threshold: 1.0
}
// 1. IntersectionObserver를 만든다
let observer = new IntersectionObserver(callback, options);
// 2. tar 만든다
let target = document.querySelector('#listItem');
observer.observe(target)
ref : intersectionObserver mdn https://tech.lezhin.com/2017/07/13/intersectionobserver-overview
Fake Loading이란.. Eager Loading
- 한번에 불러오는 것
- 서버에 query를 한번만 보내면 된다.
Lazy Loading
- 하나당 여러개의 Collection이 존재할 때 사용한다.
- 관련된 개체를 즉시 사용하지 않을 경우에 사용
- 캐싱 문제
모든 사용자들에게 똑같은 페이지를 로딩하는 경우, 캐시는 큰 도움이 될 수 있다. 하지만 페이스북, 인스타그램과 같이 쿠키에다가 사용자 정보를 담아서 사용자에 따라 다른 정보를 제공하는 페이지라면, 캐싱이 의미가 있을까? 오히려 쿼리로 캐싱을 구분하는 apollo-client가 모든 사용자에게 캐싱된 하나의 같은 페이지를 보여주게 되는 것 아닐까?
=> 먼저, 사용자 정보를 쿠키에 담지 않고, 쿼리의 인자에 담는다면 사용자마다 쿼리가 구분될 것이고 캐싱이 가능해 질 것 같다. 또한 서로 다른 사용자의 페이지 로딩에는 캐싱이 큰 도움이 되지 않지만, 사용자가 새로 고침을 할 경우 페이지 로딩에 도움을 줄 것 같다.
- 매 스크롤마다 스크롤 이벤트에 대한 콜백이 발생합니다.
- 이러한 콜백에는 많은 리소스가 필요할 수도 있습니다.
- 이벤트 핸들러가 많은 연산(예 : 무거운 계산 및 기타 DOM 조작)을 수행(이벤트 핸들러의 과도한 횟수가 발생하는 것)하는 경우 에 대해 제약을 걸어 제어할 수 있는 수준으로 이벤트를 발생(그 핸들러를 더 적게 실행하면 빠져 나갈 수 있음)시켜야 합니다.
intersection observer 를 사용해서 해당 이미지가 뷰포트에 들어올 때 로드하도록 설정
목록이 길어진 경우 뷰포트에서 사라질 경우 돔에서 랜더링하지 않도록 하는 기술로 https://ko.reactjs.org/docs/optimizing-performance.html#virtualize-long-lists 기법을 사용할 수 있다.
- 이벤트를 일정한 주기마다 발생하도록 하는 기술
- 연이어 호출되는 함수들 중 마지막 함수(또는 제일 처음)만 호출하도록 하는 것
-
회고
-
학습
-
스프린트
-
기술공유
-
회의
-
마스터클래스_정리
-
데일리마무리회의
-
데일리스크럼