recoil의 selector를 이용하여 동일한 API의 요청에 대해서는 값을 캐싱을 하여, 불필요한 API 요청을 줄여보자.
selector
란 구독하고 있는 atom
에 변화가 생길 때마다 새로운 값을 리턴해주는 순수 함수이다. 즉, get
을 통해 가져온 값은 의존성을 갖고 있어, 구독하는 값이 변할 때마다 새로운 값을 갱신한다.
이를 단순히 get
에 async
와 await
을 추가함으로서 비동기를 처리할 수 있는데, 여기서 중요한 점은 위와 반대로 구독하는 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
의 값을 필연적으로 갱신해줘야 할 경우에는 어떻게 해야 할까?
가장 단순한 방법으로는, 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() }));
따라서, 원하는 시기에 캐시를 강제로 갱신시켜주는 방법이 필요한데, 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
를 이용하여 값을 캐싱한다.
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
를 사용해서, selector
의 state
에 맞게 분기 처리해서 사용해줘도 된다.
필자는 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
good이요 ㅎㅎ