컴포넌트를 여러 컴포넌트로 나눠야 할 때

어떤 상황에서 하나의 컴포넌트를 여러 컴포넌트로 나누는 게 맞을까요?

이 포스트는 Kent C. DoddsWhen to break up a component into multiple components를 번역한 글입니다.

리액트 애플리케이션을 작성할 때 하나의 리엑트 컴포넌트로 작성해도 된다는 점을 알고 있나요? 하나의 거대한 컴포넌트 안에 애플리케이션 전체를 넣는다고 해도 기술적으로 불가능한 것은 전혀 아닙니다. 거대한 render 메소드와 엄청 많은 인스턴스 메소드, 수많은 상태가 있을 것이고 아마도 모든 생명주기 훅(lifecycle hook)을 사용해야 할 겁니다. (shouldComponentUpdatecomponentWillUnmount는 예외겠네요. 항상 상태가 갱신된 데다 컴포넌트가 언마운트 될 일이 없으니까요!)

이 방법을 사용하면 다음 문제를 마주하게 됩니다.

  1. 성능이 저하될 수 있습니다. 상태 변화가 일어날 때마다 애플리케이션 전체를 새로 그리게 됩니다.
  2. 코드 공유와 재사용성이… 쉽지 않을 겁니다.
  3. 상태 관리도 어렵습니다. 어느 상태와 이벤트 핸들러가 어느 JSX 부분에 사용되는지 보다 보면 두통 😬이 올겁니다. 그리고 버그 🐜를 추적하는 일도 쉽지 않습니다. (이 부분은 관심사 분리의 장점 중 하나입니다.)
  4. 테스트는 100% 통합 테스트입니다. 물론 반드시 나쁜 것은 아닙니다. 하지만 엣지 케이스를 테스트하기 쉽지 않고 테스트를 하려는 부분을 격리하는 일이 어렵습니다. 그래서 이런 테스트를 유지하는 것 자체가 큰 도전입니다.
  5. 여러 엔지니어와 함께 이 코드 위에서 작업하는 일은 끔찍합니다. git diff의 결과가 어떨지, merge로 발생하는 코드 충돌이 상상이라도 가나요?!
  6. 서드파티 컴포넌트 라이브러리를 사용하는 게 불가능하지 않을까요? 단일 컴포넌트에 모든 것을 작성하는 것이 목표라면 서드파티 라이브러리 사용 자체가 좀 이상한 상황이 될 겁니다. 서드파티 컴포넌트를 사용한다고 하더라도 고차컴포넌트를 사용하는 react-emotion 같은 라이브러리는 어떨까요? 쓸 수 없겠죠!
  7. 선언적 컴포넌트 안에서 명령형 추상/API를 캡슐화하는 일은 어렵습니다. 한 거대한 컴포넌트 속 생명주기 훅에 널리 흩뿌려져 있게 되어 코드를 따라가기 더욱 어려워집니다.

이런 이유에서 컴포넌트로 나눠 작성합니다. 컴포넌트를 나누면 위 문제를 해결할 수 있게 됩니다.

한동안 AMA에서 앱을 컴포넌트로 나누는 좋은 방법/패턴이 있는가에 대한 질문을 받았습니다. 이게 제 답변입니다. “만약 위에서 살펴본 문제를 직면하게 될 때, 컴포넌트를 여러 작은 컴포넌트로 나눠야 할 때입니다. 미리 나누지 말고요.” 단일 컴포넌트를 여러 컴포넌트로 나누는 일을 우리는 “추상(abstraction)“이라고 부르고 있습니다. 추상은 멋지지만 모든 추상은 비용이 따릅니다. 그래서 추상에 되려 당하기 전에 이 비용과 이득에 대해 유의하고 있어야 합니다.

중복은 잘못된 추상보다 훨씬 저렴합니다.Sandi Metz

저는 제 컴포넌트의 render 메소드가 정말 길어져도 크게 개의치 않습니다. JSX는 JavaScript 표현식의 모음이고 컴포넌트를 위해 선언적 API를 사용한다는 점을 기억하세요. 컴포넌트를 작은 크기로 나눠 이곳저곳에 프로퍼티 내리꽂기 (역자 주: 번역)를 하지 않고 render 메소드를 그대로 유지한다고 해서 그렇게 코드가 엄청 잘못되거나 하는 것은 아니라는 의미입니다.

결론

컴포넌트를 더 작은 크기로 쪼개는 것은 자유입니다. 하지만 진짜 문제를 경험하기 전까지는 크기가 커지는 컴포넌트에 대해 두려워하지 마세요. 너무 이르게 추상적으로 나누는 것보다 정말로 필요할 때까지 기다렸다가 적용하는 것이 코드 관리에 훨씬 편리합니다.