공부내용 공유하기

컴포넌트에 대한 횡설수설

컴포넌트란 무엇일까?

컴포넌트의 사전적 의미는 다음과 같다

a part or element of a larger whole, especially a part of a machine or vehicle.  
constituting part of a larger whole; constituent.


즉, 컴포넌트란 전체의 일부가 되는 구성요소이자 부분집합이다.

그렇다면 컴포넌트가 모여서 이뤄지는 그 전체란 무엇일까?

컴포넌트로 이뤄지는 완성된 전체가 무엇이냐에 따라 컴포넌트가 무엇인지도 정의될 수 있다.

컴포넌트들이 모여 만들어지는 결과물을 Application으로 정의했다면, Application의 구성요소이자 부분집합으로써의 컴포넌트는 그 자체로 작은 Application의 역할을 하게 될 것이다.

작은 Application의 역할을 하는 컴포넌트들을 구현하다 보면, 컴포넌트의 테스트가 어려워진다.

테스트가 어려워진다는 말은 곧 SRP를 지키기 어려워진다는 말과 동일하다.

SRP를 지키기 어려워지면 코드의 유지보수가 어려워지고, 코드를 보는 개발자들의 시간이 더 소요된다.

이런 복잡한 코드(+테스트가 없는 코드)는 개발자로 하여금 리팩토링에 대한 두려움을 갖게 만든다.

컴포넌트의 결과물을 Application으로 간주하면서, 각 컴포넌트의 SRP를 수호하고 싶다면 컴포넌트의 역할을 더 잘게 쪼개야 한다.

뷰에 집중하는 컴포넌트, 로직에 집중하는 컴포넌트, 서버로부터 데이터를 가져오거나 사용자의 입력된 데이터를 전송하는 컴포넌트 등으로 그 역할을 잘게 쪼개면 된다.

모든 컴포넌트를 쪼개서 테스트를 작성하고, index에 배치한다면 문제가 없을까?

배치를 하는 로직, 조건부 렌더링 로직 역시 컴포넌트의 역할이기 때문에 layout 컴포넌트를 통해 해당 책임을 부여할 수 있다.

이렇게 모든 컴포넌트를 한 파일에 둘 수 있다면 문제가 없을까...?

이런 구조는 현실에서 가능하지 않다.

작은 컴포넌트들의 유닛 테스트가 끝났다면, 또 다른 과제가 있다.

실제 세계에서는 작은 컴포넌트들의 조합으로 상호작용을 통해 비즈니스 요구사항을 처리하기 때문에 컴포넌트 간의 상호작용을 테스트하기 위한 통합 테스트도 필요하다. 데이터를 가져오는 컴포넌트와 보여주는 컴포넌트가 합쳐져서 서버로부터 데이터를 가져와서 보여주는 실제 비즈니스 요구사항에 대응하기 마련이다.

이 통합 테스트에서 가장 많이 하는 실수는 nested component들의 세부 구현에 의존하는 것이다.

저장 버튼이 있는 폼을 테스트한다고 생각한다면, 폼 컴포넌트에서 테스트를 해야 하는 요소는 무엇일까?

대표적으로 '제출 버튼을 눌렀을 때 유효성 검증'이 있을 것이다.

이 컴포넌트의 내부에 제출 역할을 하는 저장 버튼이 있다고 생각해보자.

여기서 하위 컴포넌트인 저장 버튼의 "저장" 텍스트가 중요할까?

"저장"이라는 텍스트는 전혀 중요하지 않다.

오로지 제출을 하는 버튼이라는 것이 중요하다.

테스트 코드에서 "저장"이라는 텍스트를 찾아 해당 버튼을 눌러준다면, 이 테스트 코드는 '제출 버튼을 눌렀을 때'를 테스트하는 코드가 아니라, "저장"이라는 텍스트를 가진 버튼을 눌렀을 때 테스트를 하는 코드가 되는 것이다.

전형적인 위 음성 테스트로, 이런 코드들이 쌓이면 개발자로 하여금 테스트 코드의 효용을 낮추고 회의적인 시각을 갖게 한다. 

컴포넌트를 통해 Application을 구성하려는 프론트엔드 개발자는 컴포넌트가 무엇인지를 명확히 정의하고, 어떤 역할을 부여할 것인지를 보다 엄밀하게 정의해야 한다. 내가 예시로 든 부분은 테스트 코드이지만, 설령 테스트 코드가 아니더라도 컴포넌트의 재사용성과 유연한 확장, 가독성을 위해서라도 깊게 고민해야 하는 부분이다.

이런 고민은 각 개발자 개인으로 하여금 유지보수 가능한 양질의 코드를 만들 수 있게 하는 토양이 되고, 이제 그 토양 위에서 의존성 주입 등을 통해 우리가 원하는 유연한 구조를 설계하는 실무적인 역량을 키울 수 있게 된다고 생각한다...