Request WaterFalls

RN·2024년 8월 27일

리액트

목록 보기
3/8

https://www.actitime.com/project-management/what-is-waterfall-model

다른 분야에서의 워터폴 그림이지만 의미는 같다.


이때까지 next.js 에서 swr을 사용했고, 리액트에서 react query를 사용했다가 처음으로 next.js 에서 react query를 사용했다.

react query 에서 사용하는 useQuery 는 클라이언트 사이드에서만 사용가능 하다고 하는데, 정적인 데이터를 가져올 때는 서버사이드에서 미리 받아와서 사용한다면 성능에 많은 도움이 될 것 같다.

그렇다면 react query를 어떻게 서버사이드에서 사용해야 할까? 라는 의문점이 들어

공식문서를 통해 공부하기로 했다.

1. Request Waterfalls


리액트 쿼리 또는 실제로 컴포넌트 내부에서 데이터를 가져올 수 있는 모든 data fetching library를 사용할 때 가장 큰 성능 저해 요인(footgun)은 Request Waterfalls 이다.

공식문서에서는 이렇게 설명한다. 그럼 워터폴이 뭘까

리소스(코드, CSS, 이미지, 데이터)에 대한 요청이 다른 리소스에 대한 요청이 완료된 후에야 시작되는 경우를 말한다 .

만약 브라우저가 서버에 페이지를 요청했을 때 아래의 순서로 리소스를 가져온다고 하자.

markup (여기서는 HTML) -> JS -> CSS -> ASSET

그럼 총 3번의 서버 왕복이 발생하고 각 서버 왕복당 100ms 가 소모된다면 400ms가 소모된다.

만약 이것을 줄여서 세 번째 서버요청에 CSS와 ASSET을 전부 가져오면 300ms로 줄일 수 있게 된다.


그럼 어떤 경우에 워터폴이 발생할까?


1.1 직렬 쿼리 워터폴


const { data : 해리포터정보 } = useQuery({
	queryKey : "harry-porter",
    queryFn : getHarry
})

const harryNumber = 해리포터정보?.학번

const { data : 해리포터기숙사 } = useQuery({
	queryKey : "harry-dorm",
    queryFn : () => getDorm(harryNumber)
    enabled : !!harryhNumber
}

enabled 로 인해 해리포터의 정보를 얻지못하면 학번도 얻지못해 해리포터의 기숙사 정보 역시 얻을 수 없다.

그렇기에 아래의 쿼리는 반드시 해리포터정보 를 얻은 후에만 동작하기 때문에 워터폴이 발생한다.


1.1.1 병렬로 동작하는 useQuery


참고로 useQuery 를 동일한 블럭에 연속으로 작성하면 쿼리가 병렬로 동작한다.

즉,

const {data1} = useQuery({queryKey : "어벤져스", queryFn : getAvengers}

const {data2} = useQuery({queryKey : "저스티스리그", queryFn : getJusticeLeague}

console.log("data1 : " + data1 + ", data2 : " + data2);

라고 작성하면 두 쿼리는 병렬로 동작한다.

하지만 1.1 에서 작성한 해리포터의 경우는 enabled 로 인해 첫 번째 쿼리에서 해리포터의 학번을 받아와야만 두 번째 쿼리가 동작하기 때문에 병렬이 아니며, waterfall 이 발생한다.


1.1.2 병렬로 동작하는 useSuspenseQuery


suspenseQuery 는 위와 같이 작성하면 병렬로 동작하지 않는다.

대신 아래와 같이 suspenseQueries 를 사용하면 된다.

const query1 = useSuspenseQuery({querykey : ["그리핀도르"], queryFn : getGryffindor})

const query2 = useSuspenseQuery({queryKey : ["슬리데린"], queryFn : getSlytherin})

를 아래와 같이 변경

const queries = useSuspenseQueries({
	queries : [
    	{querykey : ["그리핀도르"], queryFn : getGryffindor},
    	{queryKey : ["슬리데린"], queryFn : getSlytherin}
    ]
})

1.2 중첩 컴포넌트 워터폴


부모와 자식 컴포넌트에 모두 쿼리가 포함되어 있고 부모가 쿼리가 완료될 때까지 자식을 렌더링하지 않는 경우다.


위의 사진은 Hogwarts 컴포넌트에서 학생의 정보를 가져오고 PhotoFrame 이라는 사진 액자 컴포넌트를 가져온다. 이 액자 컴포넌트에서 역시 액자의 정보들을 호출한다.

Hogwarts 에서 호출하는 useQueryPhotoFrame 에서 호출하는 useQuery는 서로 연관성이 없어보인다.

하지만 Hogwarts 에서 isPending 으로 로딩 작업을 수행한다면 첫 번째 useQuery가 완료될 때 까지 두 번째 useQuery 는 동작할 수 없어 waterfall 이 발생한다.

위의 사진 처럼 미리 useQuery로 페치하면 워터폴을 방지할 수 있다.


1.2.1 notifyOnChangeProps 가 뭐야


번역하면 props가 변경 시 알리겠다. 라는 뜻이다. 옵저버 패턴에서 나오는 그 notify가 맞는 것 같다.

const { data, isLoading } = useQuery({
	queryKey : ["students"],
    queryFn : () => getStudents(housename),
	notifyOnChangeProps : [data]    
})

위와 같이 코드를 작성하면 useQuery가 반환하는 수 많은 메타 정보(data, isLoading, isFetching 등)이 변경되면 쿼리를 관찰하고 있는 옵저버에게 알려서 리렌더링을 일으킨다.

여기서는 data만 전달해줬으므로 data값이 변경될 때에만 리렌더링이 발생한다.

만약 notifyOnChangeProps를 설정하지 않으면 모든 메타정보가 변경될 때마다 리렌더링된다.

0개의 댓글