본문 바로가기

React

React 관심사 분리

고차 컴포넌트(High Order Component)란?

리액트 공식 문서

리액트 공식 문서의 고차 컴포넌트(HOC)에 관한 문서이다. 여기서는 HOC에 대해서는 컴포넌트를 전달받아 새로운 컴포넌트를 반환한다 정도로만 짚고 넘어가면 될 것 같다.

HOC에 대해 알아보기 전 횡단 관심사가 무엇인지 살펴보겠다.


횡단 관심사(Cross-Cutting Concerns)

https://dev.to/mstryoda/application-architecture-for-microservices-sidecar-pattern-34m6


횡단 관심사란 단일한 기능이 여러 지점에 걸쳐 나타나는 것을 말한다.

❓위의 짧은 설명으로는 여전히 횡단 관심사가 무엇이고 고차 컴포넌트와 무슨 상관이 있는지 모호하기 때문에 인증과 인가로 예를 들어보겠다.

웹 어플리케이션에서는 유저를 중심으로 관련된 여러 가지 정보를 처리하게 되므로 인증 / 인가는 중요한 관심사이며, 중요한 관심사인 만큼 애플리케이션의 곳곳에서 사용이 된다.

이렇게 애플리케이션의 곳곳에서 횡단적으로, 즉 공통적으로 관심을 가지고 사용하는 것을 횡단 관심사라고 할 수 있다. 이러한 인증 / 인가와 같은 횡단 관심사는 페칭 데이터에 해당하며, 페칭 관련 로직의 경우 비즈니스 로직에 해당한다.

❓여기서 비지니스 로직이 갑자기 왜 나오는가 싶을 수 있지만 짧게 아키텍처에 대해 언급하고 HOC에 대해 살펴보면 좋을 것 같다.

많은 경우 아키텍처라고 하면 MVC, Observer Pattern, Flux, Container-Presenter 패턴을 떠올리게 된다. 여기서 각 패턴이 무엇인지 보다는 각 패턴들의 공통점만 짚고 넘어가면 될 것 같다.

위의 패턴 외에도 이후에 나온 패턴은 View와 Model을 구분한다 라는 공통점을 가지고 있다. View는 쉽게 말해 UI, 즉 HTML과 CSS로 만들어지는 결과물을 의미하고, Model은 화면에 반영되는 데이터를 주관하는 영역을 의미한다.

과거에는 웹 프론트엔드 아키텍처의 방향이 View, Model, Controller로 구분했던 반면 현대 웹프론트엔드 아키텍처의 방향은 View와 Model로 구분하고 있다.

여기서 중요한 포인트는 View와 Model을 구분한다! 이다. 이는 화면을 보여주는 로직(UI)과 비지니스 로직(서버에서 데이터 페칭)을 구분하자! 로 연결 지으면 좋을 것 같다. 이때 사용할 수 있는 것이 고차 컴포넌트(HOC)이다.



고차 컴포넌트 사용 예시


위의 이미지는 고차 컴포넌트를 사용할 수 있는 좋은 예시이다. Apage, Bpage 두 개의 컴포넌트가 있으며, 각각의 컴포넌트에서는 유저 정보를 가져오는 공통된 관심사인 횡단 관심사를 가지고 있다. 이러한 공통된 관심사는 결국 중복된 코드로 이어지며, 위의 경우 View와 Model의 구분이 되어 있지 않다.


우선 고차 컴포넌트, 컴포넌트를 전달받아 컴포넌트를 반환하는 함수를 만들어주고, 유저의 정보를 가져오는 공통적인 부분을 usersHOC 컴포넌트에 넣어준다.

이렇게 되면 컴포넌트 usersHOC에서 작성된 비지니스 로직을 통해 페칭 된 데이터를 컴포넌트 usersHOC로 감싸준 컴포넌트의 prop으로 전달하게 된다.

결과적으로 오른쪽 이미지와 같이 고차 함수 usersHOC의 인수로 각각의 페이지를 감싸주게 되면, 각각의 페이지는 유저 정보를 prop으로 전달받아 사용이 가능해지게 된다.

❗️여기서 감싸준다라고 표현한 것은 말 그대로이다. 컴포넌트로 컴포넌트를 감싸주는 것을 의미한다. 구조로 보면 usersHOC 안의 자식으로 페이지 컴포넌트가 있는 것이 된다.

이렇게 비지니스 로직과 화면을 보여주는 로직을 구분함과 동시에 횡단 관심사였던 유저 정보 페칭 또한 중복이 사라지게 되었다.


https://velog.io/@hahahaday12/ReactHook


❗️하지만 여기서도 문제가 발생하게 된다. 횡단 관심사, 즉 공통적인 관심사가 발생할 때마다 위와 같이 고차 컴포넌트를 사용해 주게 되면 컴포넌트를 컴포넌트로 감싸주는 작업이 반복되게 되는 wrapper hells가 발생하게 된다. 이렇게 감싸주는 컴포넌트가 많이 지게 되면 데이터 흐름을 파악하기가 힘들어진다.



🎉 하지만 이러한 문제도 Hooks가 도입되면서 해결할 수 있게 되었다.

기존의 고차 컴포넌트를 사용하여 분리와 재사용을 했었다면, Hooks 도입으로 커스텀 훅스를 만들어 위와 같이 보다 간편하게 분리와 재사용이 가능해졌다. 또한 컴포넌트로 컴포넌트를 감싸는 것이 아닌 함수를 호출해 데이터를 전달받아 화면에 렌더링 시키기 때문에 wrapper hells에서도 탈출할 수 있게 되었다.

🎉이렇게 횡단 관심사들을 별도의 레이어로 분리해서 보다 일관되게 처리한다면 보다 바람직한 설계가 된다.



관심사 분리


어떤 큰일이 닥쳐서 무엇부터 해야 할지 모를 때, "잠시 멈추고 무슨 상황인지 파악부터 해보자!" 라고 생각하는 경우가 있다. 나에게 닥친 큰일을 해결해야할 때 이것 저것하는 것이 아닌 한 번에 한 가지씩 해결나가는 것이 좋은 문제 해결 방법이자 효과적인 문제 해결 방법이다.

이러한 인간의 문제 해결 방법을 코드에도 적용할 수 있다. 내가 작성한 코드는 컴퓨터가 실행하고 처리하지만, 결국엔 그 코드는 내가 짠것이기 때문이다. 즉, 내가 문제가 발생했을 때 한 번에 하나씩 해결하는 것처럼 코드도 문제가 발생했을 때 전체 코드를 변경하는 것이 아닌 일부분만 변경할 수 있도록 만드는 것이다.

⭐️ 이렇게 컴퓨터 공학에서는 '한번에 한 가지만 걱정해도 괜찮도록' 각각의 관심사에 따라 코드를 분리하는 기법을 관심사의 분리라고 한다.

관심사의 분리가 적절히 구현된 코드는 Loose Coupling(낮은 결합도, 각각의 코드가 서로 얽혀있지 않고 독립적으로 잘 분리되어 있다.)과 High Cohesive(높은 응집도, 유사한 내용끼리 비슷한 위치에 잘 모여 있다.)와 같은 특성을 발견할 수 있다.

이는 곧 앞서 언급한 유저 인터페이스(View)와 비지니스 로직(Model)의 분리가 된다. 하지만 여전히 유저 인터페이스와 비지니스 로직은 모호하게 들리기만 한다.

간단하게 정리하면 아래와 같다.

  • 유저 인터페이스
    • 사용자와 애플리케이션이 화면 상에서 상호 작용하는 영역
    • 데이터를 화면에 보여주는 영역
    • 외부에서 주입 받은 prop에만 의존하는 순수 함수 컴포넌트
  • 비지니스 로직
    • 현실의 비지니스 규칙 (예로 들면 대출에는 대출 잔액, 이자율, 지급 일정이 필요하고 유저의 신용은 몇 등급 이상이어야 한다. )
    • API 호출 로직


✨API 호출 로직을 view에서 분리하는 것만으로도 어느 정도 관심사의 분리를 달성했다고 볼 수 있다 한다!


관심사의 분리의 유사한 예시로 Headless UI라는 개념이 있다. 해당 개념은 디자인 시스템에서 스타일을 담당하는 부분과 로직과 상태를 담당하는 부분을 분리하는 것을 의미한다. 이러한 분리를 보다 거시적인 아키텍처 수준에서 고민한 결과물이 필독서로 많이 언급되는 클린 아키텍처에 정리되어 있다.

*클린 아키텍처는 자주 변하는 곳(UI, 라이브러리 등 세부사항)과 자주 변화할 일이 없는 개념(자주 변할 일이 없는 비지니스 규칙)을 계층을 나누어 관리하도록 제안하고 있다. 즉 도메인 레벨, 애플리케이션 레벨, 인터페이스 레벨로 나누어 관리를 하자고 제안을 하는 것이다.

❗️하지만 프론트엔드 쪽에 클린 아키텍처를 적용하기는 영 감이 잡히지 않는다. 그렇지만 원칙에 대해 조금이라도 생각해볼만한 가치는 있다.

💡의존성 역전 원칙에서 말하는 ‘유연성이 극대화된 시스템’이란 소스 코드 의존성이 ‘추상(abstraction)’에 의존하며 ‘구체(concretion)’에는 의존하지 않는 시스템이다. (…) 구체적이며 변동성이 크다면 절대로 그 이름을 언급하지 말라. - 로버트 C. 마틴 <클린 아키텍처>, 92~94p

위의 언급이 나 또한 감만 오지 정확히 무슨말인지는 확 와닿지 않는다. 하지만 해당 언급을 곱씹어보니 객체지향 프로그래밍이 떠올랐다.

객체지향 프로그래밍에서 중요한 개념중 하나인 추상. 타입스크립트에서는 클래스에 인터페이스 규격을 설정해 사용자가 클래스 내부의 복잡하고 많은 함수를 신경쓰지 않고 인터페이스 규격 확인만으로도 편하게 사용할 수 있게 되고 결과적으로 클래스의 추상화가 이루어지게 된다.

클래스와 클래스가 의사 소통하는 경우 직접적으로 연결시켜주는 커플링을 해버리면 코드의 유연성이 현저하게 떨어지게 되어 재사용성이 떨어지게 된다. 때문에 클래스와 클래스를 연결시켜 줄 때는 인터페이스를 통해 연결시켜주는 디커플링 원칙을 적용해주어야 한다.

예시를 들자면 카페라떼를 만드는 클래스가 있다. 클래스 카페라떼 는 카페라떼를 만들기 위해 커피와 우유를 사용하야 한다. 때문에 속성 우유클래스 서울우유를 연결시켜 주었다. 이제 클래스 카페라떼는 카페라떼를 만들 수 있지만 서울우유만 사용해야하는 클래스가 되었다.

이렇게 클래스에 클래스를 커플링하는 것이 아니라 인터페이스 우유속성 우유에 연결해주는 디커플링을 해주게 되면 클래스 카페라떼는 서울우유, 부산우유, 매일우유 등.. 그냥 우유라는 규격만 지키게되면 소통이 가능해지게 되어 유연성이 높아지게 된다.

❓내 생각이 맞는지는 모르겠지만, '구체적이며 변동성이 크다면 절대로 그 이름을 언급하지 말라.' 부분에서 무조건 우유는 사용해야하지만 서울우유, 부산우유, 매일우유 등 변동성이 큰 경우 클래스 카페라떼의 속성 우유에 서울우유를 직접적으로 언급해서 연결하는 것이 아닌 인터페이스를 사용하여 '우유이기만 하면돼!'라는 규격을 정해 연결짓는 것이 떠올랐다. (추상은 인터페이스)

'React' 카테고리의 다른 글

React 기본 정리  (0) 2023.01.10
Infinite Scroll & pagination API  (0) 2022.11.29
Redux의 State를 올바르게 사용하는 방법  (0) 2022.11.08
Redux 정리  (0) 2022.11.08
사용자 입력값 유효성 검사  (0) 2022.11.01