ET네 만물상 - GitHub Repository / 배포 링크
const getMyReviews = (): Promise<ReviewType[]> => GET("/my/reviews");
export const useMyReviews = () => useQuery(["reviews"], () => getMyReviews());
const Review = () => {
const { status, data, refetch } = useMyReviews();
return {
status !== 'loading'
? <div>
{
data.map((el)=><div>el</div>)
}
</div>
: <div>스켈레톤 UI</div>
}
}
useQuery
의 반환값에 있는 status
를 사용해서 현재 API 요청 상태가 loading인지 success인지 등을 판단해서 상태에 맞는 렌더링이 가능하다
일반적인 API 요청에 대한 로직은 default state로 렌더링을 하고, useEffect로 API 응답을 받으면 setState하는 방식인데 API를 일반 hooks처럼 사용할 수 있어서 편리하고, 깔끔하다.
위에서 얘기한 것 처럼 클라이언트 상태에 API에서 받아온 데이터를 set하는 과정 없이, 바로 data를 사용하면 된다.
컴포넌트 함수인 만큼 렌더링에 해당 data를 사용하고, 휘발되면 그만이기에 더없이 깔끔하다고 생각함 !
import { QueryClientProvider, QueryClient } from "react-query";
const queryClient = new QueryClient();
ReactDOM.render(
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
document.getElementById("app")
);
QueryClientProvider로 react-query를 사용할 영역을 지정하는데, 앱 전체에서 사용할 예정이므로 App 컴포넌트를 감싼다
QueryClient로 인스턴스를 생성해서 client prop 값으로 전달한다
QueryClient로 생성한 인스턴스는 캐시를 관리하기 위해 사용된다.
useQuery가 훅 안에서 해당 인스턴스에 접근할 수 있도록 QueryClientProvider가 필요하다.
const { status, data, error, refetch } = useQuery(
key,
requestAPICallback,
options
);
useQuery는 첫 번째 인자로 캐싱 key 배열을, 두 번째로 API요청 callback을 전달해서 호출한다.
당연한 얘기지만 requestAPICallback은 Promise 객체를 반환하는 함수여야한다.
options에는 cacheTime 같은 옵션을 추가할 수 있다.
컴포넌트에서 좁은 범위로 사용하는 상태는 모두 useState를 사용했지만 위에서 언급한 상태들은 어플리케이션 전반에서 사용 되는 상태들이다
따라서 root에서 관리할 필요가 있으며 이 경우 prop drilling 이슈가 발생하기에 전역 관리가 필요했다.
contextAPI를 사용할 수도 있었지만 보일러 플레이트를 직접 구성해야하고, 주로 useReducer와 함께 사용되는 패턴이지만 다루는 상태가 단순한 형태이기에 어울리지 않았다.
따라서 쉽게 적용 가능하고, 사용하기도 간편한 recoil을 선택했다.
사실 제대로 사용한게 아니라서 이렇다 평할 수는 없다.. selector라던가, 특히 비동기 처리에 사용하지 않아서 반쪽짜리 사용인 느낌?
다음 프로젝트에서 기회가 된다면 recoil을 사용한 비동기 데이터 처리를 해봐야겠다.
/// index.tsx
import { RecoilRoot } from "recoil";
ReactDOM.render(
<RecoilRoot>
<App />
</RecoilRoot>,
document.getElementById("app")
);
RecoilRoot
로 전역 상태를 공유할 범위를 지정한다. 우리팀의 경우 어플리케이션 전체에서 공유할 상태만 관리할 것이기에 App 컴포넌트를 감쌌다.
RecoilRoot는 중복 사용할 수 있으며, 이 경우 부모방향으로 가장 가까운 RecoilRoot
가 기준이 된다.
import { atom } from "recoil";
export const alertState = atom({
key: "isOpen",
default: {
isOpened: false,
message: "",
},
});
atom, selector 등을 이용해서 상태를 선언한다.
참고로 atom, selector는 key를 반환한다. 그렇기에 위의 예시에서 alertState를 export하고 사용하는 곳에서 키로 사용하는 것!
import { alertState } from "@/store/state";
import { useRecoilState } from "recoil";
const Alert = () => {
const [alert, setAlert] = useRecoilState(alertState);
return ..
}
사용할 model의 key를 가져오고, recoil hook 중을 용도에 맞게 가져와서 사용한다
useRecoilState : state와 setState를 모두 사용한다. 컴포넌트는 해당 상태가 변경되었을 때 리렌더링 대상이 된다.
useRecoilValue : state만 사용한다. 이 경우에도 컴포넌트는 상태가 변했을 때 리렌더링 대상이 된다.
useSetRecoilState : setState만 사용한다. 컴포넌트는 해당 상태가 변경되었을 때 리렌더링 대상이 되지 않는다.