[Recoil] selector를 이용한 API 캐싱(feat. useRecoilRefresher)

tech-hoon·2022년 3월 17일
8
post-thumbnail

recoil의 selector를 이용하여 동일한 API의 요청에 대해서는 값을 캐싱을 하여, 불필요한 API 요청을 줄여보자.

selector

selector란 구독하고 있는 atom에 변화가 생길 때마다 새로운 값을 리턴해주는 순수 함수이다. 즉, get을 통해 가져온 값은 의존성을 갖고 있어, 구독하는 값이 변할 때마다 새로운 값을 갱신한다.

이를 단순히 getasyncawait을 추가함으로서 비동기를 처리할 수 있는데, 여기서 중요한 점은 위와 반대로 구독하는 atom 값이 변하지 않을 경우, 동일한 값을 리턴한다는 점이다. 즉, 파라미터가 동일하다면, 캐시된 값을 리턴한다.

export const mySelector = selector({
  key: 'my_selector',
  get: async ({ get }) => {
    const user = get(userState);
    const posts = await getMyPosts(user.id);
    return posts;
  },
});

캐시 값 갱신

그렇다면 구독하는 atom과는 별개로, selector의 값을 필연적으로 갱신해줘야 할 경우에는 어떻게 해야 할까?

Version Parameter

가장 단순한 방법으로는, Date time stamp를 파라미터로 넣어서 의존성을 주입시키는 것이다. 참고로selectorFamily를 사용해서 selector에 원하는 파라미터를 넣을 수 있다. 마찬가지로 이 파라미터도 의존성을 갖기 때문에, 파라미터 값이 달라지면 그 값으로 갱신된다.

하지만 이 방식대로 하면, 매 API 요청 마다 새로운 API 요청으로 인식될텐데, 이는 캐싱을 사용하지 않던 기존 방식과 큰 차이가 없을 것이다.

// selector 정의부
export const productAsyncState = selectorFamily({
  key: 'productAsyncState',
  get:
    ({ ver }) =>
    async ({ get }) => {
      const idx = get(productIdxState);
      return await getProductDetail(idx, ver);
    },
});
// 호출부
const date = useRecoilValue(productAsyncState({ ver: Date.now() }));

useRecoilRefresher

따라서, 원하는 시기에 캐시를 강제로 갱신시켜주는 방법이 필요한데, recoil 0.50 버전부터 useRecoilRefresher라는 기능이 unstable 딱지를 달고 릴리즈되었다.

캐시를 갱신해줘야하는 경우에 이 함수를 호출하면, 기존 캐시를 제거하고 atom에 변화가 생기지 않더라도 새로운 값으로 갱신한다.

데이터 갱신이 필요한 곳 마다 사용할 것이기 때문에 hook으로 만들어주었다.

import { RecoilValue, useRecoilRefresher_UNSTABLE } from 'recoil';

const useRecoilCacheRefresh = (state: RecoilValue<any>) => {
  const refresher = useRecoilRefresher_UNSTABLE(state);
  return refresher;
};

export default useRecoilCacheRefresh;
const myPostsCacheRefresher = useRecoilCacheRefresh(myPostsState);

적용

위의 내용을 토대로 실 상황에 적용해보자.

게시글을 올렸을 때, 내 작성글 페이지에 갱신되어야 하는 상황이다. 게시글을 삭제했을 때도 마찬가지로, 값이 갱신되어 내 작성글에서 제거되어야 한다.

이 두 상황을 제외하면 항상 같은 데이터를 가질 것이기 때문에, 여기에 selector를 이용하여 값을 캐싱한다.

selector

get으로 로그인한 사용자 정보를 가져와 의존성을 주입시킨다. 가져온 사용자의 id를 토대로, 내 작성글을 불러와 리턴해준다.

export const myPostsState = selector({
  key: 'posts/myPosts',
  get: async ({ get }) => {
    const loginUser = get(loginUserState);
    const posts = await getMyPosts(loginUser.uid);
    return posts;
  },
});

게시글 등록 및 삭제 시 값 갱신

위에서 만든 userRecoilCacheRefresh를 통해, 강제 캐시값 갱신이 필요한 컴포넌트에서 사용해준다.

// 내 작성글 값에 변화가 이루어지는 컴포넌트

const myPostsCacheRefresher = useRecoilCacheRefresh(myPostsState);

...

const onUploadPost = async () =>{
	await uploadPost( ); // 내 작성글 업로드
	myPostsCacheRefresher();
}

...

const onDeletePost = async (id) =>{
	await deletePost(id); // 내 작성글 삭제
	myPostsCacheRefresher();
}

사용 부분

selector를 사용하면, 해당 값이 사용되는 컴포넌트를 React.suspense 로 감싸서 사용해야한다. 혹은 useRecoilValueLoadable를 사용해서, selectorstate에 맞게 분기 처리해서 사용해줘도 된다.

필자는 state가 loading일 때는 skeleton ui를 보여주고, state가 불러와졌을 때는 값을 꺼내서 사용해주었다.

// MyPosts.tsx 내 작성글 페이지

const MyPosts = () => {
  const postsLoadable = useRecoilValueLoadable(myPostsState);

  return (
    <S.Container>
      {postsLoadable.state === 'loading' ? (
        <S.PostCardSkeleton />
      ) : (
        <CardContainer posts={postsLoadable.contents} />
      )}
    </S.Container>
  );
};

결과

이처럼, 데이터가 변경되는 상황이 적고 예측이 가능한 경우에, 평상시엔 값을 캐싱하여 캐시된 값을 리턴해주고, 데이터가 변경되는 시점에는 강제로 캐시를 갱신해주는 방식으로 사용하면 불필요한 API 호출양도 줄이고, 빠른 속도로 데이터를 제공할 수 있기에 시도해보기 좋은 방법 같다.

적용 전

적용 후

참고

https://velog.io/@yiyb0603/Selector를-이용하여-데이터-값-캐싱하기
https://www.youtube.com/watch?v=0-UaleJZOw8
https://recoiljs.org/ko/docs/api-reference/core/useRecoilRefresher

profile
제 삽질을 공유합니다.

2개의 댓글

comment-user-thumbnail
2022년 6월 27일

good이요 ㅎㅎ

답글 달기
comment-user-thumbnail
2022년 10월 31일

안녕하세요 좋은 글 감사합니다 !
궁금한 것이 있어 댓글 남깁니다
해당 포스팅 내용이 react query의 캐시기능과 어떤 차이점이 있을까요?
리액트 쿼리로도 캐시가 구현이 가능한데 리코일로 캐시를 했을때와 어떤 차이점이 있는건가요 ?

답글 달기