TIL

Presentational and Container 패턴과 리액트에 대한 고찰.

해당 패턴에 대해서 아는 것이 없었는데 이번에 구매레이어 쪽 유지보수를 하면서 setShowLoading(true) 이런 로직들이 여러군데 산재해 있어 어지러워서 react의 suspense를 도입하고 싶어 찾아보다가... 결국은 현재 구조로는 도입이 불가능함을 깨닫고 슬퍼하면서 쓰는 글이다 ㅜㅜ 일단 내 수준에서는 그렇다.. 그래서 결론적으로 해당 패턴은 현재의 리액트와 정말정말 어울리지 않는 것 같다. 


내가 작업하고 있는 구매레이어는 분석해보니 현재 presentational and container 패턴으로 구성되어있다고 볼 수 있다.

모노레포로 되어있는 프로젝트에서 lib 공통모듈에 몰아두고 Context API를 사용하여 구매레이어에 필요한 함수나 상태들을 가지고 있다. 구매 레이어에 표현하는 데이터들의 상태는 최종적으로 LIb에서 관리하게 되는 것이다. md(모바일), pd(pc용) 프로젝트들에서는 오로지 컨텍스트에서 값을 뽑아서 보여주기만 한다. 


지금 내가 하고 있는 것은 구매레이어의 두번째 depth에 표현할 상태를 만드는 일이다. api에서 받아오는 속성 상태에 따라  특정 케이스에 맞는 ui를 노출하는 것!  이 작업을 하면서 구매레이어를 분석하면서 presentational and container 패턴의 단점을 매우 극명히 알게 되었다. 실제로 해당 패턴을 제안했던 아주 아주 유명한 Dan Abramov도 2019년에 이미 해당 패턴이 현재 리액트와 맞지않는다고.. 지적했다고..


첫번째로는 presentational 컴포넌트 즉, UI용 컴포넌트의 입장에서는 외부에서 데이터를 주입받아 사용하게 된다. 때문에 컴포넌트가 스스로 필요한 데이터를 컨트롤 할 수 없고 항상 변화는 외부에 의존하게 된다. 특히 props 뿐만 아니라 컨텍스트의 변수를 사용하게 된다면 외부에서 컨트롤하는 값이 나의 컴포넌트에 언제 어떻게 영향을 미칠지 알기 어렵다.

해당 패턴이 인기 있었던 건 hook이 없었던 시절이라고 한다. 로직과 view를 분리하기 위한 방법으로 등장하였으며 ui만 표현하는 presentational 컴포넌트와 비즈니스로직과 사이드이펙을 줄 수 있는 요소를 가지고 있는 container가 분리되어 작성되었다. 그러나 hook의 등장으로 굳이 이렇게 나누지 않고도 어렵지 않게 비즈니스 로직과 view를 분리하면서 같은 컴포넌트에 작성할 수 있다. 따라서 오히려 이런 패턴은 하나의 컴포넌트를 온전하기 파악하기 위해 여러 파일과 폴더를 넘나들어야하는 단점이 있다. 실제로 내가 느끼기에는 프론트 컴포넌트에서 비지니스 로직과 ui가 떨어질 수 있는 관계인가? 싶은 생각도 들기도 했다. 어차피 보고 싶고 봐야할 건 둘 다였다. (실제로 구매레이어의 투뎁스를 파악하기 위해 대여섯개의 파일들을 본 것 같다 ㅜㅜ) 디버깅 하기에도 한층 더 까다롭다..

두번째로는 첫번째와 비슷하긴 한데 Suspense나 useTransition, ErrorBoundary 같은 기능을 사용하기 어렵다. 통신에서 데이터를 받아와서 작업하는 로직이 컴포넌트 외부(현재의 내 경우에는 공통 쪽..)에 있다보니 데이터 페치 여부, 로딩상태 등까지 외부에서 컨트롤 하게 된다. 따라서 직접 promise 또는 error를 던져야하는 Suspense나 ErrorBounday는 사용하기 어렵다. 



사실 구매레이어 투뎁스에서만 사용하는 공통으로 빠진 함수를 구매레이어 내부로 끌고 오고 싶은 마음이 굴뚝같다. 그러면 앞으로 react의 새로운 기능들도 쓸 수 있는 가능성을 열어줄 수 있겠지.. 하지만 펑션 하나에 다른 펑션들이 줄줄이 있어... 필요한 것만 다시 내부로 끌고 오려고 하면... 시간이 부족하다ㅜ 

일단 내가 할 수 있는 것만 했다.

첫번째로 공통쪽 로직에 빠져있는 구매레이어 2단용 함수 안에서 사용하는 비동기 함수 두개가... 각각 모두 try catch로 걸려있어 에러를 핸들링하기 매우 까다로웠던 부분을 수정하였다. 비동기 함수 1번을 조회한 결과값을 가지고 2번을 조회해야하는데 1번 2번 모두에게 try catch가 걸려있어 묶어서 에러를 핸들링할 수 없었기 때문에 try catch를 모두 제거하고 1,2번을 감싼 함수 하나에서만 try catch를 사용하도록 변경하였다. 사실 잘 돌아가는 소스의 try catch를 제거한다는 게 이게 맞다고 생각이 드는데도 좀 불안했다.

그런데 이 영상을 참고하니 더욱 내 방향이 맞구나 싶었고 많은 깨달음을 얻었다. 또한 기존의 setShowLoading(true) setShowLoading(false)가 여기저기 산재한 소스를 보고 이 영상을 보니 더더욱 많이 배울 수 있었다. 

 


두번째로는 setShowLoading을 그래서 어떻게 풀었냐이다. 진짜 눈감고 공통에 다시 잘 덮어 넣어둘까 싶었는데 내 눈에 흙이 들어가도.. 이건 안되겠다 싶었다..  공통에 넣어두면 fetch전에 무조건 true fetch 끝나면 false 이것밖에 못하는데 상당히 거슬렸다. 애매한 시간에 걸리면 로딩바가 번쩍떴다 지워지는데 거슬려서 (혼자) 참을 수 없었다.. 

2단 레이어에서 결국 공통쪽에서는 과감히 로딩바 노출 제어를 빼고 컴포넌트안에서 useEffect 걸고 타이머를 써서 걸어보기로 했다. 대신 공통에서는 오류인 케이스를 별도로 처리할 수 있는데(catch 안에서) 컴포넌트안에서는 오류를 직접 감지하지 못하기 때문에.. 오류에 대한 state는 넘겨줘야했다. 그래서 오류면 무조건 로딩바 없이. 오류가 아니라면 0.7초 이상 걸리면 로딩바 보여주게끔 아니라면 로딩바 없이 보여주게끔 작업했다. 

0.7초 이상 걸리면,, 그 다음에 0.7초를 또 기다려야하긴 하는데.. 이건 일단 내 선에는 더 이상 좋은 방법이 생각나지 않았다... 
잠시.. 타이머를 왜 썼지? useEffect에서 에러만 감지하면 되는 거 아니야..?

일단 여기서 만족해보자.


암튼 최대한 앞으로는 훅을 써서 컴포넌트 안에서 데이터를 제어하자~

'TIL' 카테고리의 다른 글

scroll 이벤트가 발생하지 않는 경우  (0) 2024.03.13
타입스크립트에서 상수를 안전하게 쓰는 법  (0) 2024.02.26
1.30  (0) 2024.01.30
업무 중 참고한 사이트 01.26  (0) 2024.01.29
업무 중 참고한 사이트 1.29  (0) 2024.01.29