- 키워드: react v18, suspense, ErrorBoundary, SWR, msw, mock API
- 상황
- React 18에 새로 추가된 Suspense와 기존의 ErrorBoundary 기능을 조합하여 사용해보고, 그 장점을 파악한다.
- 해결과정
- Suspense를 사용하기 앞서, data fetching과 렌더링을 수행하는 Block 컴포넌트를 작성한다.
// Block.tsx import useSWR from 'swr'; import React from 'react'; const fetcher = (url: string) => fetch(url).then((res) => res.json()); function Block({ label }: { label: string }) { const { data } = useSWR(['/delay', label], fetcher, { suspense: true }); if (data.errorMessage) { throw new Error(data.errorMessage); } return ( <p className="blockContent"> {label} <span style={{ color: 'blue' }}>{data.message}</span> </p> ); } export default Block;
Block 컴포넌트는 SWR을 이용하여 '/delay'라는 GET API를 호출한다.
data fetch에 성공한다면 'Good'이라는 message가 출력되고, 실패한다면 errorMessage를 throw한다.
delay mock API는 msw 라이브러리를 사용하여 간단하게 구현했다. (https://mswjs.io/)
'/delay'는 임의의 시간을 기다린 후에 '200 success' 또는 '500 error'를 return 한다.
// mocks/handlers.js import { rest } from 'msw'; export function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } export const handlers = [ rest.get('/delay', async (req, res, ctx) => { await sleep(Math.random() * 2000); const isSuccess = Math.random() > 0.2; if (isSuccess) { return res(ctx.status(200), ctx.json({ message: 'Good' })); } else { return res(ctx.status(500), ctx.json({ errorMessage: '서버 오류입니다.' })); } }), ];
React v18의 Suspense를 이용하기 위해서 SWR의 suspense mode를 설정해주었다. ({ suspense: true })
이제 Block Component의 부모 컴포넌트에서 Suspense를 걸어줘야한다. - 첫번째 상황으로 Block 4개를 일렬로 각각 렌더링하는 'Individual.tsx' 부모 컴포넌트를 작성한다.
Block 컴포넌트 바로 위에 Suspense와 ErrorBoundary를 적용한 것을 확인할 수 있다.// Individual.tsx import React, { Suspense } from 'react'; import ErrorBoundary from '../ErrorBoundary'; import Block from './Block'; function Individual() { return ( <section className="blocks"> {['ONE', 'TWO', 'THREE', 'FOUR'].map((label) => ( <div className="blockWrapper" key={label}> <ErrorBoundary fallback={<p style={{ color: 'red' }}>Error</p>} key={label}> <Suspense fallback={<p>loading...</p>}> <Block label={label} /> </Suspense> </ErrorBoundary> </div> ))} </section> ); } export default Individual;
ErrorBoundary는 리액트 공식문서에서 제공하는 코드를 가져왔다.
(https://ko.reactjs.org/docs/error-boundaries.html)
// ErrorBoundary.jsx import React from 'react'; class ErrorBoundary extends React.Component { state = { hasError: false, error: null }; static getDerivedStateFromError(error) { return { hasError: true, error, }; } render() { if (this.state.hasError) { return this.props.fallback; } return this.props.children; } } export default ErrorBoundary;
'Individual.tsx' 의 렌더링 결과는 아래 사진과 같다.
4개의 Block Component에 대해 각각 'loading Suspense'가 작동하고, 응답값이 오는 즉시 Good(200 success) 또는 Error(500 error시)를 표시한다. - 두번째 상황으로 Block 4개를 하나로 묶어 Suspense와 ErrorBoundary를 걸어보자.
'Individual'과 비슷하게 'Integration' 컴포넌트를 만든다.
// Integration.tsx import React, { Suspense } from 'react'; import ErrorBoundary from '../ErrorBoundary'; import Block from './Block'; function Integration() { return ( <ErrorBoundary fallback={ <div className="boundary" style={{ color: 'red' }}> Error </div> } > <Suspense fallback={<div className="boundary">loading...</div>}> <section className="blocks"> {['ONE', 'TWO', 'THREE', 'FOUR'].map((label) => ( <div className="blockWrapper" key={label}> <Block label={label} key={label} /> </div> ))} </section> </Suspense> </ErrorBoundary> ); } export default Integration;
Individual과 마찬가지로 4개의 Block을 렌더링하지만, 이번에는 Suspense와 ErrorBoundary를 4개의 Block에 한번에 묶어서 걸어주었다.
'Integration'의 결과는 아래와 같다.
4개의 Block 중 하나라도 loading(Suspense) 또는 Error(ErrorBoundary) 상태일 경우, Integration 전체가 하나의 단위로 묶인다.
4개의 Block이 모두 성공하면 각각 정상적으로 Good이 표시된다.
- Suspense를 사용하기 앞서, data fetching과 렌더링을 수행하는 Block 컴포넌트를 작성한다.
- 의의
- Suspense와 ErrorBoundary를 사용해보니, 함수형 프로그래밍의 장점을 리액트 컴포넌트에 접목시켰다는 느낌이 들었다.
기존에는 custom hook/HOC을 이용하여 로딩/에러 상태를 분리하거나, isLoading/isError와 같은 상태값을 추가하여 관리하였다. 그리고 이러한 방법들은 로딩/에러 상태와 각각의 컴포넌트 사이의 결합성이 존재했다.
반면 Suspense와 ErrorBoundary를 이용할 경우, 컴포넌트는 '성공' 상태일 때의 결과에만 집중하여 개발할 수 있다. 로딩 상태와 에러 상태인 경우에는 그 사실을 위로 throw 하기만 하면 되기 때문이다. 이 때, throw한 상태를 어디에서 catch할 지도 개발자가 마음대로 정할 수 있고 그 위치를 수정하는 것도 쉽다는 것을 알게되었다.
- Suspense와 ErrorBoundary를 사용해보니, 함수형 프로그래밍의 장점을 리액트 컴포넌트에 접목시켰다는 느낌이 들었다.
'프론트엔드 > React' 카테고리의 다른 글
Pagination Component를 선언형으로 구현하기 (0) | 2022.11.30 |
---|---|
React 18 useDeferredValue로 성능 최적화하기 (0) | 2022.05.20 |
react-window로 렌더링 성능 최적화하기 (0) | 2022.04.02 |
UI library에서 spacing system props 구현하기 (0) | 2022.03.22 |
스탬프 투어 미션 구현하기(feat. firestore, redux saga) (0) | 2022.03.07 |