로딩 컴포넌트(스피너) 적용을 위해 리액트 서스펜스(Suspense), 커스텀 훅스 사용해보기

    코린이의 로딩 컴포넌트 적용기


    오늘은 UX 개선을 위해 스피너를 만들어서 로딩중일 때 표시하도록 할 것이다.

    그런데, 로딩 컴포넌트를 어떻게 화면에 보여줘야하지? 🤔🤔

     

    1. 매 페이지마다 로딩 컴포넌트를 import 하고 로딩중일 때 보여주는 방법? (조건부 렌더링)

    정말 단순하면서도 바로 떠오르는 방법이었다.

     

    2. 하이오더 컴포넌트를 통해 재사용하는 방법? (리액트 고-급 기술)

    이 방법으로 컴포넌트를 사용해본 적은 없지만, 로직 재사용을 위해서 HOC 사용을 고민해볼 수 있을 것 같다.

    언젠가 사용해보고 싶은 기술이다..!

     

    3. 리액트 서스펜스(Suspense)사용하기

    이런게 있는 줄도 몰랐는데 리액트 16.6 버전 부터였나 서스펜스를 사용하여 구현할 수도 있다고 한다. (오... 그게 뭔데..!)

     


    💙 로딩 컴포넌트 구현기

     

    1. 로딩 스피너 만들기 

    로딩에 사용할 CSS는 Loading.io에서 우리 사이트에 맞는 컬러와 내 취향을 일부 반영하여 수정해보았다.

     

    2. useLoading 커스텀 훅 만들기

    이렇게 만든 훅을 어디에서 호출해서 사용할까?에 대해 생각해보게 됐다.

     

     

    지난 첫 프로젝트 때 어떻게 했었는지 잠시 구경하러 갔다. 그 중에서도 민망한 내 코드를 가져왔다.... 부끄러운 과거의 나

    (옵셔널 체이닝을 남용하고 있어서 코드 가독성의 상태가.. 😥)

     

    아무튼, 이때는 팀원 분께서 로딩 컴포넌트를 만들어주셨었는데

    RTK query를 사용하고 있어서 API 요청 시 로딩 상태를 쉽게 확인할 수 있었다.

     

    하지만, 이번엔 RTK Query를 대신 axios를 사용하고 있으므로 로딩 상태를 확인하려면 직접 로직을 짜야한다..

    따라서 위와 같은 형태로 로직을 짜야 한다면, 서버와 통신할 때 로딩 상태인지 확인할 수 있는 코드가 필요할 것 같다.

     

    그렇다면 axios를 사용할 때 어떤 방법이 있을까?

    1. axios 인터셉터 사용

    2. 페이지에서 서버 통신 request, response할 때마다 useState 사용하기

     

    만약 1번 방법인 인터셉터를 사용한다면?

    1. axios request 발생 시 로딩중(true)으로 변경

    2. axios response 발생 시 로딩중(false)으로 변경

     

    그리고 실패! 😭

    에러 발생

    axios의 interceptor를 사용하여 요청과 응답을 가로채서 axios 요청과 응답 전에 호출할 수 있도록 해야겠다!고 생각했지만,

    axios의 인터셉터 안에서 useState를 사용할 수 없다는 문제가 있었다.

    context API를 사용해서도 시도해봤지만, 마찬가지로 리액트 hook의 규칙을 위반한다는 에러메시지가 출력되어 실패했다.

     

    axios 인터셉터 버려! 리액트 서스펜스로 간다.

    로딩 컨텍스트도 안녕.. 나는 다시 커스텀 훅을 사용할게..

    서스펜스는 자식 컴포넌트가 로드 완료될 때까지 fallback에 넣은 컴포넌트로 대체할 수 있다.

     

    💎 코드 스플리팅

    코드 스플리팅이란, 필요한 파일만 로드하도록 파일을 작게 나누는 것을 의미한다.

    즉 초기 페이지 로드할 때 모든 코드를 로드하는 것이 아니라 해당 페이지에서 필요한 코드만 로드한다는 것이다.

     

    서스펜스를 사용하기 전, 코드 스플리팅을 진행하고자 한다.

    우선 import 문을 수정해야해서 다음과 같은 고민을 한 차례 해보았다.

    현재 Router는 위와 같이 페이지를 import하여 사용하고 있다.

    여기서 코드 스플리팅을 위해 React.lazy()를 사용하게 되면 각각을 분리해야 하므로

    안그래도 지금도 지저분한데 엄청나게 지저분해질 것 같았다.

     

    예를 들면, 다음과 같은 형태가 될 것이다.

    오.. 세상에 import 문으로 페이지를 꽉 채우겠어요. 🤣

    그러므로 import문 분리를 실시한다.

     

    자 드가자

    pages 폴더의 index.ts로 위치를 옮겨주었다.

     

    그리고 Router 에서는 import문을 한 줄로 변경해주었다.

    이후 Route 컴포넌트의 element 프롭스는 다음과 같이 고쳐준다.

     

    pages라고 이름을 부여했으므로 pages 폴더의 index.ts에 있는 애들은 pages.~ 로 바꿔주면 된다.

     

    서스펜스로 Routes를 감싸주었다.

    이제 느린 3G, 빠른 3G로 변경하여 로딩창 화면을 확인해보았다.

     

    각 페이지의 컴포넌트가 렌더링 되기 전까지 로딩 컴포넌트가 나타난다. (로딩 컴포넌트의 위치는 수정해야겠네요. 깔깔..)

    홈 화면(메인 페이지)의 경우는 서버 통신 코드가 있어서 상단 앱바와 하단 앱바만 먼저 나타나는 것 같은데

    서버 통신을 하는 부분에서도 로딩을 따로 관리해야 할 것 같다.

     

     

    빠른 3G

    눈에 거슬리는 로딩 컴포넌트의 위치를 재배치했다. (편안-)

    메인 페이지에서 서버로부터 get요청을 하는 비동기 코드가 있어서 여기서 테스트 해보기로 했다.

     

    하지만 메인 페이지는 팀원분이 작성한 코드라서 함부로 추가해도 되..되겠죠(?)

    어차피 로딩 적용하려면 사용해야 하니 잠시 마음대로 추가를...!🙇‍♂️

     

    getMainData라는 함수 내부에서 비동기로 사용되고 있어서 앞서 useLoading 커스텀 훅을 만든 친구를 import했다.

    (여기선 리액트 서스펜스 대신 커스텀 훅을 사용했다.)

     

    try-catch 구문 추가 후 showLoading을 try 전에 호출해주고 catch 바깥에 hideLoading을 추가했다.

    동작시켜보자.

     

    적용 전 / 적용 후

     

    차이점이라 한다면 상단 앱바와 하단 앱바가 먼저 렌더링 된 후에도 로딩 컴포넌트가 나타난다.

    메인 페이지에서는 get 요청으로 서버에서 데이터를 받아와야 하기 때문에

    서스펜스가 페이지별 로딩을 처리하고 상단, 하단 앱바만 나타났을 때 비동기 서버 통신 요청 동안

    흰 페이지가 나타나는 문제를 개선하고자 이때도 로딩을 띄우도록 커스텀 훅스를 사용하여 처리했다.

     

    원래 이 부분도 리액트 서스펜스를 사용해볼까 했지만 원하는대로 동작을 하지 않았다..! 🤣

    일단은 전체 페이지 로드에만 서스펜스를 사용하고,

    서버 통신이 필요한 부분은 아직 내겐 커스텀 훅스를 사용하는 방법이 더 편할 것 같아서 결국 코드를 롤백했다..ㅠㅠ

     

    찐찐 막으로 코드를 한번 더 수정했다. finally 를 추가해야 할 것 같았기 때문.

     

    차후 생각해볼 과제 🤔

    우선 첫 번째로 다른 기능 구현을 마치면 로딩 컴포넌트를 보여주는 부분을 어떻게 개선할지,

    개선방향에 대해 다시 고민해봐야겠다.

    뭔가 아쉽다고 해야할까. 로딩 컴포넌트 어떻게 보여줄지 오늘 하루종일 붙잡고 있었는데 동작 결과는 원했던 대로긴 한데끝내기 참 아쉽다는 생각이 드는 건 분명히 개선 사항이 있을 것이다...! 하지만 지금은 모르겠다!

    또, 서스펜스에 대해 서치하던 중 찾아본 블로그 글에 의하면 if 조건문을 사용하는 명령형 코드에 대한 문제점도 있을 것 같다.

     

    Race condition 고민

    예전에 레이스 컨디션 때문에 고생했던 적이 있었다.

    예를 들면 서버에서 받은 이미지 여러개를 파일로 변환하는 과정에서 발생했던 이슈였다.

    물론 이 문제는 해결했지만, 비동기에서 발생할 수도 있는 레이스 컨디션에 대해서는 한번쯤은 생각해봐야할 과제가 아닐까.🙄

     

    아무튼 당장은 API를 사용하는 부분에서 모두 로딩을 처리할 예정...

    그 전에 해결하고 싶은 개선사항이 하나 더 눈에 띄어서 그것부터 해결하러 가야겠다! 갈 길이 멀다아아

    댓글