왜 SWR 을 선택했는가?

Arenacast·2022년 4월 11일
1

react

목록 보기
3/3
post-thumbnail

Data fetching

프론트앤드 사이드 라이브러리(리엑트)에서 서버쪽에 데이터를 요청하고 응답을 받는 방법에는 여러가지가 있습니다.

1. UseEffect

  1. 가장 대표적인 예로 useEffect hook 내에서 쓰는 경우 일것입니다.
function RenderComponent() {
  	const [data, setData] = useState<T | null>(null)
    const [loading, setLoading] = useState<boolean>(true)
    const [error, setError] = useState<boolean>(false)
  
	useEffect(() => {
      (function() {
        fetch(url, options)
  			.then((response) => {
        		setData(response)
        	})
  			.catch((error) => {
        		setError(true)
        	})
        	.finally(() => { 
          		setLoading(false)
        	});
      })()
    }, 
              
    if (loading) return <div>loading...</div>
  	if (error) return <div>error...</div>
  	return <div>data</div>
}

이러한 방법은 데이터 요청에서 문제가 없지만 데이터를 패치하는 컴포넌트에서는 데이터의 상태(로딩, 에러, 성공)를 신경써야합니다. 데이터를 패치하는 컴포넌트를 작성할때마다 위의 보일러 플레이트 코드를 작성하는 것은 굉장히 비효율적이고 추후에 관리면에서도 관리가 어려워질수 있습니다.

이를 해결하기 위해 나온것이 useFetch 라는 custom hook 형태입니다.

2. useFetch ( custom hook )

// useFetch.ts
function useFetch(url) {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState<boolean>(true);
  const [error, setError] = useState<boolean>(false);
  
  useEffect(() => {
      (function() {
        fetch(url, options)
  			.then((response) => {
        		setData(response)
        	})
  			.catch((error) => {
        		setError(true)
        	})
        	.finally(() => { 
          		setLoading(false)
        	});
      })()
  }, [url])

  return { data, loading, error }
}

export default useFetch;
import useFetch from './useFetch'

function RenderComponent() {
  	const {data, loading, error} = useFetch(url)
              
    if (loading) return <div>loading...</div>
  	if (error) return <div>error...</div>
  	return <div>data</div>
}

useFetch 훅을 만들고 난 후부터는 데이터 패치를 해야하는 컴포넌트에서는 useFetch 만 import 하고 데이터 패치 상태는 모두 useFetch 에서만 관리하고 혹시 상태 로직을 수정하고 싶으면 useFetch 에서만 수정하면 됩니다.

하지만 useFetch 만으로 해결 못하는게 있는데요. 그건 바로 패치 데이터를 전역상태로 관리하고 싶으면?? 예를 들어서 user 의 정보(닉네임, profile 사진)는 많은 컴포넌트에서 써야할 데이터일것입니다. 이럴때마다 useFetch 를 써서 서버에 데이터를 요청해야 할까요? 서버에 수많은 요청을 보내게 되고 서버는 부하가 될것입니다.

3. Redux saga, Redux thunk

전역 상태관리와 비동기 데이터 요청을 모두 해결하기 위해 나온것이 redux 라이브러리 redux saga, redux thunk 등입니다.
아래 예제는 redux saga 의 코드만 적어두었습니다.

// saga.js
function* handleRequestUser() {
  while (true) {
    const action = yield take(REQUEST_USER);
    const { payload, error } = yield call(API.user, action.payload);
    if (payload && !error) {
      yield put(successUser(payload));
    } else {
      yield put(failureUser(error));
    }
  }
}

export default function* rootSaga() {
  yield fork(handleRequestUser);
}

위의 코드를 간단히 설명하면 fork effect 로 handleRequestUser saga task 를 시작합니다. REQUEST_USER 의 액션 타입이 dispatch 가 되면 api 요청을 한후 성공시에는 성공액션 (successUser)을 dispatch 하고 실패시에는 실패액션(failureUser)을 dispatch 합니다. 어떤가요?? 이렇게 redux saga 를 이용하면 비동기 데이터 요청 후 데이터와 상태가 redux 전역 스토어에 관리가 될것입니다.

하지만 모든 api 요청에 성공, 상태 액션 타입이 들어가야 하며 그에 따라 작성해야할 보일러 플레이트 코드도 늘어날것이고 (물론 redux toolkit 같은 라이브러리가 있지만), 그리고 다음 swr 때 설명을 드릴거지만 특정 시간 간격으로 데이터 패칭을 하거나, 클릭시 데이터 패칭을 하는 기능 또한 따로 구현을 해야할것입니다.

4. Swr

자 이제 swr 을 소개하려고 합니다. swr은 위에서 언급했던 단점들을 모두 커버할수 있습니다.

  1. useSwr 훅으로 간단히 구현 가능
  2. 데이터 패치 상태 ( react 의 suspense )
  3. 전역적으로 데이터 관리 ( swr cache )
  4. swr 의 기능
    • 클릭시 자동으로 데이터 refetch (revalidateOnFocus)
    • 특정 시간 주기로 데이터 refetch (refreshInterval)
    • 2가지 제외하고 더 많은 swr option

아래 코드는 픽앤코 포털사이트 코드 일부를 발췌해 간단히 수정하였습니다.

function LiveStreamingContents() {
	const isDesktop = useIsDesktop();
	const { data } = useSwr('/liveStreaming', fetcher, { suspense: true, revalidateOnFocus: true, refreshInterval: 5000 });

	return (
		<Slider className="container" dots infinite speed={500} slidesToShow={isDesktop ? 3 : 4} slidesToScroll={1}>
			{data?.map((matches) => (
				<Content key={matches.match_id} matchTeams={matches.match_name} gameCode={matches.game_code} leagueName={matches.league_name} />
			))}
		</Slider>
	);
}
import LiveStreamingContents from './LiveStreamingContents';

function LiveStreamingInfo() {
	return (
		<section className={styles.slider}>
			<h2 className={styles.slider_title}>P&G 라이브 정보</h2>
				<ErrorBoundary fallback=<div>error!</div>>
              		<Suspense fallback=<div>..loading</div>>
              			<LiveStreamingContents />
                	</Suspense>
              	</ErrorBoundary>
			
		</section>
	);
}

export default LiveStreamingInfo;

일단 간단하게 코드를 설명을 하면 LiveStreamingContents 컴포넌트는 현재 방송되고 있는 live 방송 정보를 서버로 부터 요청 하고 받은 데이터를 렌더링 하고 있습니다. 여기서 주의 깊게 볼것이 useSwr 의 option 으로 넘긴 suspense 입니다. suspense 를 true 로 해주면 react 의 suspense 컴포넌트는 데이터를 받아오기 전까지 suspense 의 fallback 컴포넌트가 렌더링 됩니다 (react suspense). 선언적 컴포넌트를 작성해주면 LiveStreamingContents 컴포넌트는 상태는 신경쓰지 않고 오직 데이터에만 집중하고 데이터를 렌더링하게 됩니다. 훨씬 직관적이겠죠?

추가적으로 swr 에서 제공하는 다양한 기능이 있습니다. 예시 코드에서는 revalidateOnFocus, refreshInterval 두가지만 적용하였는데 fetch option
에서 더 많은 기능을 확인할수 있습니다.

다른 브라우저 텝을 클릭후 다시 기존의 텝을 클릭하게 되면 revalidateOnFocus = true 로 인해 자동으로 api 요청을 하게 됩니다.

또한 swr 은 한번 서버에서 fetch 한 데이터는 swr cache 에 저장하여 cache 에 같은 key 로 저장된 데이터는 전역적으로 다른 컴포넌트에서 쓸수 있습니다

Swr 적용

현재 개발중인 픽앤고 포털사이트에서는 현재 각 플랫폼(twitch, afreeca)에서 방송중인 lol 과 pubg 라이브 방송 정보를 서버에서 받아 오고있습니다. 사용자가 어떠한 이벤트를 주지 않으면 라이브 방송 정보는 업데이트가 자동으로 되지 않습니다. 이렇게 주기적으로 api 를 refetch 해야 하는 경우 swr 의 option 중에 특정 시간을 주기적으로 데이터를 refect 할수 있는 기능(refreshInterval) 를 조정하여 특정 시간을 주기로 데이터 refetch 를 자동으로 하여 최신의 라이브 방송 정보를 사용자에게 제공할수 있습니다.

by James

참고
https://swr.vercel.app/

profile
arenacast team blog

0개의 댓글