Search

[Tip] useEffect 제거하기

tldr; useEffect는 로직을 시간(렌더링 순서)과 ‘엮임’을 만들어내기 때문에 코드의 복잡도를 기하급수적으로 높임.
기본적으로 React의 컴포넌트 렌더링은 만약 UI를 구성하는 propsstate가 동일하다면 여러 번 반복되더라도 결과가 달라지지 않야야 합니다. 이러한 성질을 멱등(Idempotent, 연산을 여러번 반복해도 결과가 달라지지 않음)하다고 합니다. 동일한 인자를 전달하면 항상 일정한 결과물을 리턴하는 순수 함수와도 같습니다.
하지만 렌더링 로직만으로는 우리에게 필요한 제품을 만들 수 없습니다. React의 state를 기준으로 React와 상관없는 요소들을 제어하거나, 서버 연결을 설정하거나, 컴포넌트가 화면에 나타날 때 분석 목적의 로그를 전송할 수도 있습니다. useEffect는 순수한 렌더링 과정 속 외부 세계로의 탈출구 역할을 합니다.
그러한 useEffect에 혹시 문제라도 있나요?
네. 아래 밈은 (비록 글자는 CSS이긴 하지만) useEffect를 잘못 사용해 코드가 꼬였을 때의 느낌을 잘 나타내고 있는데요. 저런 식으로 한번 꼬인 코드 앞에서는 전의를 상실하게 되는 때가 많았던 것 같습니다.
최초의 동작은 단순했을지 몰라도 useEffect의 의존성 배열에 걸려 있는 하나의 상태 변화가 또 다른 상태 변화를 유발했을 때, 다시 여러 개의 useEffect의 동작이 유발되기 시작하면 코드를 수정하기 굉장히 곤란해 집니다.
이렇듯 useEffect의 의존성 배열을 통해 트리거 하는 동작이 많아질수록 코드가 복잡해지고, 코드가 복잡해질 수록 제품은 개발자의 통제를 떠나게 됩니다.
그러면 useEffect를 어떻게 벗어날 수 있을까요?
개발자가 직접 렌더링을 생각하지 않아도 되는 프로그래밍 기법을 사용해야 합니다. 데이터 호출에 useSuspenseQuery를 사용하는 등의 방식이 있을 것 같습니다.
앞서 언급했듯 상태 관리는 결국 복잡도와의 싸움입니다. 상태를 폭발시키지 않기 위해서는 서로 상호작용 하는 상태를 줄이고, 이를 관리하기 쉬운 단위로 나누어 정복해야 합니다.
대표적인 예시로 React 프레임워크 중 하나인 Remix는 웹 앱의 구조를 아래 세 단계로 나눌 것을 제안했습니다.
1) UI를 담당하는 Component
2) UI에 필요한 데이터 의존성을 호출하는 Loader
3) UI에서 서버 레이어로 데이터를 전송하는 Action
자칫 복잡할 수 있는 로직의 덩어리를 이렇게 세 개의 큰 관심사로 나누면 이해하기도 관리하기도 쉬워질 것입니다.
그리고 Loader가 페이지 초기화에 필요한 데이터 의존성을 미리 처리해준 뒤, Promise를 풀어 컴포넌트로 내려주기 때문에 비동기 데이터의 초기화를 위해 useEffect를 쓸 필요가 없어집니다.
모던 React 개발 상황에서는 대부분의 경우 1) 비동기 데이터에 대해 컴포넌트 초기화를 하거나 2) 특정 액션이 일어났을 때 상태 변화를 감지하여 동작하게 할 때가 많은데, 대부분의 경우 이 사례처럼 useEffect를 제거할 수 있는 방법이 있습니다. 아예 없앨 수는 없어도 적어도 격리해서 사용할 수 있는 방법이 있습니다.
-
정리해보겠습니다. 우리는 코드를 읽을 때 문자 그대로 읽지 않습니다. 특정한 의미 단위로 묶어가며 읽습니다. 뇌에 가해지는 인지 부하를 덜 수 있다는 장점이 있습니다. 청킹(Chunking)이라고도 합니다.
문제를 풀 때 코드의 모델을 명시적으로 사용하는 것은 두 가지 장점이 있다. 첫째, 모델은 프로그램에 대한 정보를 다른 사람과 공유할 때 유용하다. 상태표를 만들어서 다른 사람에게 보여주면 각 단계에서의 변수의 값을 통해 코드가 어떻게 작동하는지 이해하는 데 도움이 된다. (…) 모델의 두 번째 장점은 문제를 풀 때 도움이 된다는 점이다. 두뇌에서 한 번에 처리할 수 있는 한계에 도달했을 때 모델을 만들면 인지 부하를 줄일 수 있다. - 100p
역할 별로 덩어리를 나누고, 각 덩어리가 서로 엮이지 않도록 프로그래밍 해야 뇌가 쉴 수 있습니다. 적절한 도구를 선택해 지친 개발자의 뇌를 쉬게 해주세요.

참고 자료