Suspense를 적용하려는 이유
- jotai, react-query를 사용했음에도 불구하고 삼항 연산자로 로딩 컴포넌트를 보여주고 있었기 때문에 해당 기술 스택들을 제대로 사용하지 못하는 것 같았다. 이 기술들을 사용하여 로딩 기능을 구현하고 싶었다.
- 이전에는 삼항 연산자로 로딩 페이지를 렌더링했다. DOM이 아예 사라졌다가 다시 나타나는 것이기 때문에 Layout 비용이 많이 들 수 있다.
- MainPage를 렌더링하고,
useEffect
로 데이터를 가져온 후, 재렌더링하는 것은 비동기 요청이 병렬적으로 수행될 수 있다. 이 때문에 경쟁 상태(race conditions)에 취약할 수 있다.
- 현재 MainPage에서는
useEffect
와 useState
로 비동기 통신의 순서를 정해놓은 효과를 주고 있지만, 후에 비동기 통신이 추가 된다면 우리가 생각한 순서대로 데이터가 응답된다는 보장이 없어질 수도 있기 때문에 싱크가 맞지 않는 데이터를 제공할 수도 있다.
- Promise.all을 통해 해결할 수 있지만 React에서 Suspense라는 더 좋은 방식을 제공한다.
- 데이터 로딩과 UI 렌더링이라는 두 가지 역할이 MainPage 컴포넌트 안에서 복잡하게 얽혀있다.
- 나중에 코드가 더 많아지면 읽기 어려워지고 테스트 코드를 작성하기 어려울 것이다.
적용 전 상태
로딩 컴포넌트 렌더링
// MainPage.tsx
...
return (
{isLoading ? (
<MapLoading />
) : (
<Map
latitude={coordinates!.latitude}
longitude={coordinates!.longitude}
/>
...
);
isLoading
이라는 state가 true이면 MapLoading 컴포넌트를 보여주고, false일 경우 Map 컴포넌트를 보여준다.
비동기 처리
// MainPage.tsx
useEffect(() => {
// 사용자가 위치 정보 제공에 동의했을 때
const success = (geolocationPosition: GeolocationPosition) => {
...
setMapCenter(
geolocationPosition.coords.latitude,
geolocationPosition.coords.longitude
);
};
// 사용자가 위치 정보 제공에 동의하지 않았을 때
const error = () => {
setCoordinates({ ...DEFAULT_COORDINATES });
};
navigator.geolocation.watchPosition(success, error);
}, []);
- Map 컴포넌트에서 props로 전달하는
latitude
와 longitude
는 MainPage
가 처음 렌더링 될 때 알아낸다.
- 사용자 위치 정보 제공에 동의하면
success
함수로 가서 사용자 위치 정보를 가져와 setCoordinates
해준다.
- 사용자 위치 정보 제공에 동의하지 않으면
error
함수로 가서 default 값을 setCoordinates
해준다.
// MainPage.tsx
const setMapCenter = async (latitude: number, longitude: number) => {
// 위도, 경도가 있는 경우 네이버 API에 주소 요청
const usersLocationResponse: UsersLocationResponseTypes =
await apis.getUsersLocation(latitude, longitude);
const userLocation = usersLocationResponse.results[0].region.area1.name;
...
setCoordinates({ ...DEFAULT_COORDINATES });
};
useEffect(() => {
if (!coordinates) {
return;
}
// 서울 중심 여부 혹은 사용자 위치 정보 허용 여부에 따라 다른 초기 위치 랜더링
setIsLoading(false);
}, [coordinates]);
setMapCenter
는 비동기로 사용자의 위치를 가져온다.
coordinates
에 값이 넣어지면 로딩이 끝나고 Map 컴포넌트를 보여준다.
// App.tsx
...
function App() {
return (
<>
<QueryClientProvider client={queryClient}>
<GlobalStyle />
<MainPage />
</QueryClientProvider>
</>
);
}