SWR로 infinite scrolling 구현하기 + 기타등등

이민석·2023년 2월 12일
0

Frontend

목록 보기
5/5

useSWRInfinite

웹 페이지에서 검색 결과를 탐색할 때, 처음에는 일정한 수의 검색결과만 표시되다가, 스크롤을 끝까지 내리면, 새로운 검색결과들이 로딩되는 것을 본적이 있을 것이다.
Instagram이나, Facebook에서도 위와 같은 방식을 사용하는데, 이를 Infinite scrolling이라고 부른다.

SWR은 infinite scrolling UI를 구축하기 위해 useSWRInfinite Hook을 제공한다.

import useSWRInfinite from 'swr/infinite'

// ...
const { data, error, isLoading, isValidating, mutate, size, setSize } = useSWRInfinite(
  getKey, fetcher?, options?
)

parameter

  • getKey : 인덱스와 이전 페이지의 데이터를 받고, 페이지의 키를 반환하는 함수
  • fetcher : getKey의 return값을 인자로 받고, data를 반환하는 함수
  • options : 초기에 로드해야하는 페이지 수 등 다양한 옵션

Return Value

  • data : 각 페이지의 data가 배열로 묶여있는 배열 (2차원 배열)
  • size : data를 가져올 페이지
  • setSize : 페이지의 수를 설정하는 함수
const { data, mutate, setSize } = useSWRInfinite(
    (index) => `/chats?perPage=20&page=${index + 1}`,
    fetcher,
);

위와 같이 useSWRInfinite Hook을 작성하고,

const isEmpty = data?.[0]?.length === 0;
const isReachingEnd = isEmpty || (data && data[data.length - 1]?.length < 20) || false;
const scrollbarRef = useRef<Scrollbars>(null);

const onScroll = useCallback((values) => {
    if (values.scrollTop === 0 && !isReachingEnd) {
      console.log('가장 위');
      setSize((prevSize) => prevSize + 1).then(() => {
        // 스크롤 위치 유지
        if (scrollRef?.current) {
          scrollRef.current?.scrollTop(scrollRef.current?.getScrollHeight() - values.scrollHeight);
        }
      });
    }
  }, []);

//...

<Scrollbars autoHide ref={scrollRef} onScrollFrame={onScroll}>
  

scrollbars에 scrollRef와 onScroll 함수를 위와같이 생성해주게되면, 스크롤을 할때마다, 20개씩 데이터가 로딩되고, 20개의 데이터가 모두 스크롤 되어 끝에 도달하면, 또다시 20개의 데이터를 불러오는 infinite scrolling이 구현된다.

css - float

floating layout은 과거에 flex와 grid가 나오기 전에, 많이 사용되던 layout이다.
일반적으로 CSS가 없는 화면에서, 각 component들은 위에서 아래로 나열된다.

css의 속성 중 하나인 float은 특정요소를 일반적인 컴포넌트 흐름에서 벗어나, '떠있게' 하도록 만드는 속성이다.

float속성은 left , right , none중 하나의 value를 가질 수 있다.

float은 이미지와 텍스트의 배치를 조화롭게 만들기 위해서 생성되었다.


위는 float 속성이 적용되지 않은, 이미지와 텍스트의 모습이다.
float : left 속성을 적용하면, 다음과 같이 배치가 바뀌게 된다.

float : right 속성을 적용하면, 다음과 같이 배치가 바뀌게 된다.

float을 사용하면, float요소의 부모컴포넌트가 float된 자식요소의 높이를 계산하지 못해서, 자식요소가 부모요소 밖으로 넘치는 현상이 발생한다.
이런경우에, 이어서 오는 컴포넌트들의 레이아웃이 무너지기 때문에, clear속성을 적용해서 이후 배치되는 컴포넌트들이 float의 영향을 받지 않도록 해야한다.

clearfix 방법은 여러가지가 있지만, 가장 널리 쓰이는 방법은 CSS의 가상 클래스를 이용한느 것이다.

.parent:after{
	content : "",
    display : block;
    clear : both;
}

float된 요소의 parent에 위와같은 속성을 적용하면 되는데, 가상으로 빈 형제 컴포넌트를 생성하고, 그 컴포넌트에 clear 속성을 주는 방식이다.

//float을 사용하면, block 엘리먼트는 float된 요소의 뒤로 들어가서 가려지고, inline 엘리먼트는 옆에 나란히 배치되는 것 같다(텍스트처럼). 이유는 잘 모르겠다 ㅜㅜ

dayjs

기존에는 날짜 관련 라이브러리로 moment.js를 많이 사용했지만, immutable함이 중요시 되어서, dayjs를 많이 사용한다고 한다.

달과 요일은 0부터 시작하니, 주의해야할 듯 싶다.

react-mentions

카카오톡과 같은 채팅앱에서, @를 입력하면, 사용자를 언급하는 추천이 뜨는 기능이 있다.
react-mentions는 위와같은 사용자 언급시 추천기능을 제공하는 라이브러리이다.

npm i react-mentions

import {MentionsInput , Mention} from 'react-mentions

<MentionsInput value={this.state.value} onChange={this.handleChange}>
  <Mention
    trigger="@"
    data={this.props.users}
    renderSuggestion={this.renderUserSuggestion}
  />
  <Mention
    trigger="#"
    data={this.requestTag}
    renderSuggestion={this.renderTagSuggestion}
  />
</MentionsInput>

trigger는 mention을 일으키는 고유문자로, @로 설정하게되면, input에 @를 입력할경우 추천이 뜨게된다.
data는 추천해줄 사용자의 data로, iddisplay를 가지는 object의 배열이다.
renderSuggestion은 추천되는 각 항목을 어떻게 렌더링할지 정하는 parameter로, React.Node를 제공하면 된다.

const renderSuggestion = useCallback(
    (
      suggestion: SuggestionDataItem,
      search: string,
      highlightedDisplay: React.ReactNode,
      index: number,
      focus: boolean,
    ): React.ReactNode => {
      if (!memberData) return;
      return (
        <EachMention focus={focus}>
          <img
            src={gravatar.url(memberData[index].email, { s: '20px', d: 'retro' })}
            alt={memberData[index].nickname}
          />
          <span>{highlightedDisplay}</span>
        </EachMention>
      );
    },
    [memberData],
  );

위와같이, 사용자의 이미지 아이콘과, 이름을 함께 렌더링하도록 만들 수 있다.

localStorage

클라이언트에 data를 저장하고 싶은 경우 localStorage를 이용할 수 있다.
F12를 눌러서 개발자 도구를 켠 후, Application 탭에 들어가면, 좌측에 LocalStorage를 확인할 수 있다.

localStorage.setItem(Key , Value)로 localStorage에 값을 저장할 수 있다.
localStorage는 텍스트만 저장할 수 있기 때문에, object나 array를 저장하는 경우, JSON.stringfy 함수를 이용해서 데이터를 json으로 바꿔서 저장해야 한다.

localStorage.getItem(Key)로 저장된 데이터를 읽을 수 있다.
마찬가지로, json으로 저장한 경우, JSON.parse 함수를 이용해서 데이터를 읽어들이면 된다.

localStorage.removeItem(Key)로 저장된 데이터를 지울 수 있다.

localStor

optimistic UI

optimistic UI는 사용자의 특정 요청이 성공할 것이라고 가정하고, 먼저 요청의 결과를 보여주는 방식의 UI이다. 사용자의 요청이 높은 확률로 성공하는 경우, 요청을 보내고 서버에서 응답을 받기까지 기다리기보다, 사용자에게 먼저 성공하는 결과를 가짜로 보여준후, 이후 요청이 실패한경우, 요청이 실패했다고 알려주는 방식이다.
우리가 instagram이나 facebook에서 좋아요를 누르는 경우를 생각해보자. 실제로, 화면에서 좋아요가 입력됐다는 요청의 결과(하트의 색깔이 칠해지는등)는 서버로부터 응답을 받기 이전에 이루어진다.
사용자는 마치, 좋아요 요청을 보낸 직후 좋아요가 반영됐다는 착각을하게되며, UX가 향상될 것이다.
그러나, 실제로 좋아요 요청의 결과는 요청을 보낸 후 1~2초 뒤에 받게된다. 이는 좋아요 요청이 97~99%의 경우에 성공할 수 있기 때문에 사용할 수 있는 것이며, 만약에 실패할 경우를 대비해서, 좋아요 요청이 실패했을 경우, 다시 좋아요 버튼을 원래대로 되돌려 놓는 등의 대비책을 구성해두어야 한다.

...

제로초님의 Slack 클론코딩 프론트 React 강좌를 완강했다.
2.23까지 Slack 클론코딩 벡엔드 Nest 강좌를 완강할 계획이다.

출처
https://developer.mozilla.org/ko/docs/Web/CSS/float
https://swr.vercel.app/ko/docs/pagination

0개의 댓글