seeyouletter / seeyouletter-fe

0 stars 0 forks source link

tasks을 통한 업데이트 로직을 최적화한다 #82

Closed JengYoung closed 1 year ago

JengYoung commented 1 year ago

💌 설명

어떻게 보면 성능 개선일 수도 있고, 버그 개선일 수도 있는 아이러니한 feature 였다. 사실 버그로 보는 것이 타당하다. 이유는, 첫 렌더링이 느린 이유를 탐색한 결과, pageDB.current의 렌더 여부가 먹지 않았던 것이 원인이었다.

이에 관한 내용은 매우 길다. 따라서 이 피쳐가 호기심이 생긴다면 하단의 논의해볼 사항에서 확인하는 것을 추천한다.

TL;DR

  1. 기존의 버그를 개선했다.
  2. 기존 버그를 개선한 결과 custom-hook에서 useEffect를 잘못 관리했고, 결과적으로 약 1.5초의 변경 소요 시간이 발생했다.
  3. useEffect 최적화를 진행하여 블록 변경의 소요 시간을 1.5 -> 0.2~0.3초로 개선했다.
  4. 초기 페이지에서 useEffect로 인해 리렌더링되는 횟수를 8번에서 4번으로 줄여냈다.

📎 관련 이슈

💡 논의해볼 사항

What is problem?

여태까지 모르고 있었는데, 초기 생성이 느리다고 판단한 것은 순전히 버그 때문이었다. 지금까지의 버그는 다음과 같이 렌더링이 마치 자동으로 되는 것처럼 착각하게 만들었다.

  1. pageDB가 마운트가 된다. 이때 null 값을 갖고 있다.
  2. 따라서 함수에서 리턴했을 때 당시에는 초기의 값인 null이다.
  3. 비동기로 인해 업데이트가 되었음. 이때, db에 접근 가능한 주소값을 가지고 있다.
  4. 그런데 여기서, useRef는 리렌더링을 유발하지 않는다. 따라서 리턴 값을 반환하지 않는다.
  5. 이를 가져야 할 다른 곳들에서 값이 업데이트가 되지 않아 작동하지 않는다.
  6. mousemove를 할 때 window의 값을 가져와야 되고, 이것이 적힌 page의 상태 전체가 리렌더링된다. 이때 ref값을 다시 가져와서 db에서 tasks를 꺼내게 되는 것이다.

보통 테스트를 할 때 마우스를 움직이기 때문에, 자동으로 되는 줄 알았던 블록 렌더링이, 알고 보니 수동으로 되던 상황이었다. 따라서 6번을 최대한 빠르게 작동하도록 마우스를 요리조리 움직이면, 사실 지금의 성능보다 더 빠른 상황이 나온다(?!)

하지만 버그는 버그. pageDB의 업데이트를 알리는 isMount를 넣는다면?

이는 생성에서 매우 빠르게 렌더링된다. 즉 여기까지만 보면 생성 로직에 관한 업데이트 로직은 최적화할 필요가 없다는 결론이 나온다 하지만 진짜 문제는 다음이다.

잠깐, 수정하는 데 뭔가 이상한 걸...?

이는 영상으로 대체하려 한다. 보면 알 것이다. 매우 끊기는 현상과 업데이트 직전에서의 상태들이 잔상처럼 보이는 게 반복되어, 매우 불편한 UX를 가져오고 있다.

https://user-images.githubusercontent.com/78713176/220877583-99e8da43-fa6f-4974-9da9-b32da8fba8f8.mov

거의 업데이트 시에만 1초, 잔상이 보이는 현상만 0.5초로 해서 총 1.5초의 시간이 소요된다.

원인 - useEffect 지옥

원인을 탐색한 결과, useEffect에서 비동기가 동시에 호출되는 것이 원인이었다. useTaskHIstories는 다양한 블록에서 나온다. 이때 useEffect가 초기에 발생하는 로직이 함수 내부에 존재하는데, 이 함수가 async - await로 호출하는 것이 문제였다. 따라서 약 11개의 블록이 존재하면, await 로직에서의 값 * 11이 반드시 소요 시간에 반영되는 것이었다. 그러나 useTaskHistories를 삭제하기에는, 재사용하는 컴포넌트들에서 요긴하게 쓰여지고 있어서, 이를 해결해야 했다.

해결 - 모듈의 세부 역할에 따른 분리 및 전역 상태 관리

useTemplateTasksInit이라는 커스텀 훅을 추가로 만들었다. 이는 초기에 만들어질 tasks를 딱 한 번만 렌더링하기 위함이다. (indexedDB에 필요한 건데, react-query를 쓸지말지는 고민 중이다. 흠.)

또한, atom으로 pageDB에 관한 상태들을 전역에 관리함으로써 재사용하는 곳에서 굳이 비동기로 호출하지 않도록 만들었다. 결과적으로 업데이트할 때마다 약 22번씩 나오는 useEffect의 side effect를 초기 4번까지 줄여냈으므로 성능 개선이라고도 할 수 있다.

변경 결과를 보면, 변경 시 깔끔한 것을 알 수 있다! https://user-images.githubusercontent.com/78713176/220882062-e5cd2e05-d935-4ea3-aa8b-ac208a925184.mov

추가 1. 타입 재사용

models에, indexedDB를 사용하면서 반복되어 사용될 것 같은 타입들을 모두 넣어 재사용하였다.

추가 2. useEffect 최적화

8번 렌더링되는 로직을, useEffect가 넣어지는 렉시컬 환경을 고려하며 최적화한 결과 약 50%(4번)만 리렌더링되도록 성능을 개선했다. (useEffect를 한데 모으기에는 초기에 설정되어야 하는 상태값들이 독립적이어서, 더이상 최적화는 불가능하다.)

📝 참고자료

⚠️ 잠깐! 한 번 체크해주세요.