Randomly 프로젝트 - React Query 비동기 처리하기
🏡 GitHub : https://github.com/soohyun-dev/Randomly
그동안 RTK를 통해 전역으로 관리하던 상태들을 React-Query로 캐싱하여 값을 다루는 로직으로 변경하였다.
해당 기능을 구현하면서 useQuery의 비동기처리를 어떻게 처리할 것인지에 대해 고민이 되었고,
그 과정을 담아 본다.
📌 변경된 상태 다루기
그동안 나는 RTK를 통해 폴더, 질문, 멤버, 질문 순서, 응답자 순서 등등 을 전역적으로 관리하였다.
위 값들은 store에서 기본적으로 다루고 있는 값들이다.
이렇게 다루니 컴포넌트의 벽을 허물고 전역적으로 상태들을 다룰 수 있어서 무척 편리했다.
하나둘 편해서 store에서 다루는 값들을 늘려가다보니 거의 모든 상태값들을 전역적으로 다루게 되었다.
하지만, 이렇게 처리를 하다보니 새로운 고민이 생겼다.
1. 불필요하게 많은 여러번의 API 요청
RTK에 전체 폴더에 있는 질문들을 모두 저장해두지 않고 한 폴더 내에있는 질문들과 멤버들을 각각 question 값과 member 값으로 다뤄주었다.
그러다보니, 폴더 변경이 생길경우 해당 question과 member 값을 새로 dispatch 해서 초기화 시켜줘야했고 그렇게 하려면 database에 요청을 새로 보내 선택한 폴더의 데이터들을 끌어오게 되었다.
즉, 폴더 변경시마다 계속적으로 API 요청을 하여 데이터를 끌어오고 있던 상황
코드를 예로 들면, 폴더가 변경될 때마다 해당 getPackages 함수가 동작한다.
이후 packageData 변수에 데이터를 담아오고 dispatch 를 통해 선택한 폴더의 정보를 갱해준다.
사실 이 작은 프로젝트에서 시간지연을 유발한다던가, 불편한 점은 없었다.
하지만, 뭔가 개선할 수 있는 상황이라면 개선을 해보는것이 좋지 않을까?
React-Query 라는 기술을 알고 있었고, 해당 기능을 사용하면 한 번 불러온 데이터를 캐싱해둬서 필요할 때마다 API 요청을 하는 것이 아닌 캐싱된 값을 이용하면 더 나은 성능을 보일 수 있겠다고 생각했다.
그래서 바로 이 React-Query를 적용해보기로 결정!
2. 상태값들의 의존성 문제.
기존 store에는 orderNumber라는 이름 순서가 담긴 정보와 result라는 질문 배분 번호의 정보들이 추가적으로 담겨있었다.
이 값들은 따로 노는 것들이 아닌 해당 폴더의 질문들 정보가 담긴 question 값과 member 값과 함께 변경되고 움직여야했다.
예를 들어, member에 담긴 값이 4명이라면 orderNumber도 그에 맞게 4명에 대한 순서 정보를 담고 있어야한다.
하지만, 전역변수로 값을 다루다보니 orderNumber와 result를 사용하지 않는 페이지에서 question 값과 member 값이 변경된 뒤에 다시 페이지로 돌아오게되면 서로의 정보들이 맞지 않아서 에러를 내버렸다.
그래서, 각각의 경우를 생각해서 초기화 선언이 필요해졌고 이 점을 계기로 전역변수로 굳이 다룰 필요가 없는 값들을 전역변수로 관리하게 되면 나타나게 되는 문제점들에 대해 생각하게 되었다.
3. 컴포넌트별로 존재하던 같은 API 요청.
Manage페이지나 PlayInterview 페이지에서 폴더의 질문, 멤버 정보들을 알기위해 매 폴더 변경시 컴포넌트내에서 API 호출을 선언하여 호출해 주고 있었다.
같은 로직의 함수를 동일하게 작성해서 호출하는 경우들이 생겼고, 재사용성, 코드의 전체 길이를 고려하여 따로 hook 함수로 빼내서 관리할 필요를 느꼈다.
위 문제들을 해결하고자 React-Query와 함께 정의할 함수들을 hooks 폴더 내에 생성해주기로 결정하였다.
useFolder, useMember, useQuestion 과 같이 커스텀 훅과 같이 작성하여 해당 파일 내에 useQuery를 작성하였다.
😱 비동기처리의 에러 발생
useFolder.tsx
새로 생성한 useFolder 훅 내에서 useQuery를 통해 getFolder 함수를 실행해주었다.
같은 파일 내에 선언된 API 호출 함수인 getFolder는 전달받은 folderInfo를 가지고 firebase에서 데이터를 가져오는 비동기처리 과정을 가진다.
나는 해당 과정의 순서를 체크하기위해 내부에 conosle.log 를 찍어주었고, 이후 파일을 실행시켰다.
그런데 웬걸?
내가 찍은 console.log 는 찍히지도 않고 심지어 반환된 folder 데이터값이 undefined로 할당되어서 페이지에서 사용되고 있었다.
위 콘솔에 찍은 값은 차례대로 다음과 같다.
console.log(folder)
console.log(user, folder)
folder 는 useQuery가 반환한 값
user는 store에 저장된 로그인 유저 닉네임이다.
console.log(user, data) 이다.
user는 위와 같고,
data는 useFolder가 최종적으로 반환하는 값들중 폴더의 정보가 담긴 data 값이다.
이후 마지막에 비동기 함수내에 찍었던 console.log 값이 마지막으로 나오는 것을 확인할 수 있었다.
내가 놓친것은 바로 useQuery가 비동기 함수로 동작한다는 것이다.
당연히, API 호출 로직인 getFolder를 async await 을 사용하고 있기 때문에 해당 api를 통해 값을 전달 받기 전까지 undefined를 folder에 할당한 뒤 리턴하여 다음 동기적인 동작들을 실행하고 있었다.
이 비동기 로직으로 인한 문제점은 바로 다음에 발생하였다.
PlayInterview.tsx
PlayInterview.tsx 에서 useFolder를 사용하여 folders 값을 초기화 시켜주고 있었는데, 비동기 로직으로 인한 folder 데이터가 undefined인 상태에서 그 다음 로직인 useQuestion이 불러지는 것이다.
useQuestion도 useFolder와 같은 react-query 로직을 담고 있다.
하지만 여기선 folderId 값을 필요로한다.
(folderId는 각유저가 생성한 폴더의 고유한 Id 값이다.)
이 folderId는 useFolder내에서 동작하는 dispatch를 통해 갱신 한 뒤 사용되어야 하는데,
현재 useFolder내의 dispatch 로직은 folder 값이 undefined인 관계로 동작하지 못하고 있다.
(애초에 folder 데이터를 기반으로 동작하므로 데이터가 할당 되기 전까지는 불릴 수가 없다)
따라서 이 시점에 store에 담긴 folderId의 initialState의 값은 빈값.
당연히 이 값을 파라미터로 넘겨 useQuestion을 호출하니 다음과 같이 쿼리 에러를 뿜는다.
그러면, 딱 드는 생각은 그럼 folder!==undefined 라는 조건 문을 걸고 조건을 충족했을 때 useQuestion을 부르면 되지 않을까?
하지만, 이렇게 구현하면 다음과 같은 에러가 반겨줄 것이다.
모든 Hook 호출은 컴포넌트의 렌더링 과정에서 동일한 순서로 호출되어야 한다.
React에서 Hook 호출 순서는 정해져 있다.
해당 순서를 변경해버리면, React는 state나 lifecycle 메소드와 관련해서 오류를 발생시킬 수 있다.
따라서, 컴포넌트 내부에서 조건문, 반복문을 통해 Hook을 호출해서는 안된다.
결국, 동기적으로 동작해야하는 로직 안에서 새로운 방안이 필요했다.
🫡 해결
useQuestion.tsx
queryEnabled 라는 변수를 선언해서 user 값과 folderId 가 있는지 없는지를 구분해주었다.
folderId가 없다면 collection은 호출되면 안된다. 따라서, 삼항연산자를 통해서 user값과 folderId가 있는 경우에만 collection이 호출되게 변경하였다.
이 코드로 인하여 다음과 같은 경우들이 대응되게 된다.
- 비로그인시 접근할 때 => user 값과 folderId 값이 null && undefined 이다.
- 비동기로직으로 인한 undefined 값 처리
- 폴더 데이터가 없는 유저
또한, useQuery hook 내에서는 세번째 인자로 'enabled' 옵션을 추가해주었다.
해당 옵션을 사용하면 folderId가 존재할 때만 userQuery Hook을 실행하게 되어 더욱 안전하게 접근할 수 있다.
question 데이터가 undefined 인 경우에 대해서도 대응해주었다.
return 값으로 question.data===undefined 라는 조건문을 통해 삼항연산자를 작성하였고,
만약, 반환할 데이터들이 undefined로 초기화되어있다면 초기값을 따로 설정하여 빈 배열을 반환하게 하였다.
이때 isLoading 값을 true로 넘겨주어, 아직 API 호출이 진행될 예정임을 알려주었고 빈 배열을 우선 넘긴후 동기적인 처리를 마친뒤, 비동기 로직으로 데이터를 받아오면 재 return 하여 리렌더링하는 식으로 유도하게 하였다.
해당 로직을 작성하니, 여태 발생했던 오류들이 없어졌으며 PlayInterview의 전체 코드를 짧게 다룰 수 있게 되었다.
React-Query 적용 전
PlayInterview.tsx 내에서 작성된 RTK dispatch 로직들을 모두 useQuery로 리팩터링했다.
React-Query 적용 후
훨씬 가독성이 좋아졌으며, 재사용성이 좋아졌다.
👨💻마침말
기존에 모든 값을 전역으로 다루려던 시도는 편리함을 추구하기는 했지만, 또 다른 문제점을 낳았습니다.
이번 경험을 통해서 전역변수로 다루면 편리한 값들과 굳이 전역으로 구분하지 않고 해당 컴포넌트 내에서만 사용되고 관리되는 값들을 구분할 수 있는 계기가 되었습니다.
또한, React-Query를 통해서 hook의 재사용성을 높이고 비동기 처리로 인해 발생할 수 있는 오류들을 대응하는 것에 조금 더 발전할 수 있게 되었습니다.
중요한 것은 전역으로 다룰 값과 아닌 값을 잘 구분하고, props 를 내려주는 것에 거부감을 없애야 한다는 것입니다.
적절하게 상태 값들을 잘 사용해서 프로젝트를 구성하려고 해보았습니다.
이 방법이 최선이 아니라고 생각합니다. 또, 개발을 하면서 느끼는 점들이 있으면 또 정리해보겠습니다.
'성장기록 > 프로젝트' 카테고리의 다른 글
Remind - 인플루언서 체험단/협찬 관리 서비스 프로젝트 (0) | 2023.04.11 |
---|---|
Randomly 프로젝트 - 팀원 피드백 기능, React로 모달창 구현 (0) | 2023.03.30 |
Randomly 프로젝트 - Storybook 적용기 (0) | 2023.03.19 |
Randomly 프로젝트 - React-Query 적용기 (0) | 2023.03.19 |
Randomly 프로젝트 - 카테고리별 균등 분배 기능 구현 (0) | 2023.03.11 |