개발에는 관심사의 분리(Seperation of Concerns)이라는 용어가 있다.
이는 좋은 코드를 짜기 위한 가장 기본적인 원칙이며, 더 좋은 애플리케이션을 만들기 위한 여러 디자인 패턴, 기법, 아키텍쳐 등은 결국 모두 이 관심사의 분리를 가장 기본적인 원칙으로 삼고 있다.
관심사
를 간단히 말하면 하나의 모듈
이 수행하고자 하는 목적
이다. 여기서 모듈이란 함수, 클래스 등의 단위로 해석할 수 있다.
즉, 관심사의 분리란 각 모듈들이 한번에 여러 관심사를 처리하려고 하지 않고, 하나의 관심사만 처리하도록 분리하는 것
을 의미한다.
그렇다면 왜 관심사를 분리해야 할까? 하나의 모듈에서 여러 기능을 할 수 있으면 좋아보이는 것 같은데 왜 하나의 모듈은 하나의 관심사만 처리해야 할까?
관심사를 분리하면 하나의 모듈은 하나의 목적만 가지게 된다. 하나의 목적만 가지게 된다는 말은 이 코드가 수정될 이유는 한가지만 존재하게 된다는 의미이다.
우리가 개발하는 것은 결국 코드로 구성된 소프트웨어를 만드는 것이고, 이는 하드웨어보다 더 유연하고 수정하기가 쉽다. 이러한 이점을 가지고 있으나 잘못된 설계로 기존의 코드를 수정하기 어려운 상황이라면 하드웨어와 다를바 없는 것이므로, 좋은 소프트웨어 일수록 기존의 기능을 수정하거나, 확장하기 쉬운 코드이어야지 된다. 이를 흔히유지보수
라고 부른다.
즉, 관심사를 분리하는 이유는 소프트웨어의 특정 부분이 변경되는 이유를 한가지로 제한하기 위해서
이다. 만약 여러 모듈들이 여러 관심사를 동시에 다루고 있다면 특정분야에 대해서 수정을 해야 할 때 관련된 모든 모듈을 수정해야 할 것이다.
예를 들어, 애플리케이션 내에서 인증&인가에 대해서 모든 모듈들이 관여하고 있다면, 추후 인증&인가의 동작을 수정해야 할 경우에는 모든 모듈들을 일일이 돌아다니며 수정을 해야 할 것이다.
하지만, 인증&인가를 다루는 핵심 모듈을 한가지로 제한해두고 나머지는 이 모듈을 사용하는 형식으로 설계되어 있다면 추후 인증&인가의 동작이 변경되었을 경우에는 해당 모듈만 수정하면 되기에 변화에 유연하게 대응할 수 있게 된다.
이처럼 관심사의 분리는 소프트웨어를 만드는 프로그래밍에서 가장 기본이 되는 원칙이다. 이러한 원칙을 개념적으로 구체화한 것들이 몇 가지가 있다.
단일 책임 원칙
(Single Responsibility Principle): 관심사의 분리와 유사한 개념이지만, 관심사란 표현 대신 책임이란 용어를 사용한다. 각 모듈들은 책임(수행해야 하는 동작)을 가지고 있으며 각기 하나의 책임만을 가져야 한다는 원칙이다.
KISS
(Keep It Simple, Stupid): 각 모듈들은 간단하고, 단순하게 만들라는 의미로서 여러 기능을 포함시키면서 복잡하게 만들면 유지보수가 힘들어지기에, 하나의 기능만 수행하도록 하라는 의미이다. SoC, SRP등의 원칙과 유사한 의미를 가지고 있다.
React v16.8에서 Hook이 발표되고 난 후 React에서 컴포넌트를 선언하는 방법의 대세는 클래스 컴포넌트에서 함수 컴포넌트로 옮겨져왔다. 많은 사람들이 익숙하고, 이미 기존에 많은 코드들이 작성된 방법인 클래스 컴포넌트에서 함수 컴포넌트로 옮겨온 이유중에는 함수 컴포넌트의 문법이 더 단순하고, 교착상태로 인한 버그가 발생하지 않는다는 장점도 있지만, Custom Hook
의 편리함과 활용성도 큰 비중을 차지하고 있다.
그렇다면 리액트가 가진 관심사는 무엇이 있을까? React는 UI를 구축하기 위한 라이브러리
이다. 따라서, 리액트가 가진 핵심적인 관심사는
위 두가지로 나눌 수 있다.
이 중 UI
는 실제 코드상에서는 JSX
라는 형태로 표현된다. 그리고 로직
은 유저의 입력에 반응하고, API를 호출하고, 스크린의 변화에 반응하는 등 여러 동작들을 통해서 UI에 영향을 미치는 행위
라고 할 수 있다.
초창기에 유명해진 기법인 Presentational - Container
패턴이다. Presentational - Container 기법은 컴포넌트를 크게 두 계층으로 분리하는 방법이다.
Container
는 로직들을 다루는 부분
으로 UI에는 관여하지 않고 오로지 UI를 구성하고 변화하기 위한 로직에만 집중하는 컴포넌트이다.
Presntational
은 반대로 로직은 상관하지 않고 UI가 어떻게 구성되어야 하는지에만 집중
하는 컴포넌트이다.
이렇게 컴포넌트를 두 계층으로 나누어서 Presentational을 Container로 감싼 후, 필요한 정보들과 로직을 모두 props로 전달해주는 형태로 설계하는 방법이 Presentatinal - Container 패턴이다.
이 패턴은 Hook이 등장하기 전까지는 관심사를 분리하는 표준 패턴으로 사용되었다.
하지만 Hook이 등장한 후에는 더이상 Presentational - Container 패턴을 많이 사용하지 않는다.
왜냐하면 Custom Hook
이란 기법이 발명되었고, 이것이 더 효율적으로 관심사를 분리할 수 있다고 판단되었기 때문이다.
처음 리액트를 배웠을 때는 Presentational - container 기법으로 배우고, 프로젝트를 수행한 경험이 있었다. 로직을 담당하는 container와 jsx 부분을 담당하는 presenter 파일을 나누어서 각자의 역할에 집중 할 수 있는 부분도 있었지만, 내가 느끼기에 불편한 점이 있었다.
presenter 컴포넌트와 container 컴포넌트의 의존성이 너무 강해지는 경우가 많기 때문에, 특정 로직을 따로 분리하여 재사용하기에는 필요한 변수나 함수들이 딸려 들어오는 경우가 많아, 비슷한 로직이나 UI를 재활용하기에 어려움이 많아 코드가 중복되는 경우가 많았다.
단순한 기능(버튼 클릭으로 UI 변경, input 값 변경 등)을 패턴 방식에 맞춰 적용을 한다면, 굳이 container 파일에서 함수를 선언하고, 값을 props로 던져서 presenter 파일에서 적용을 시켜야 하는데, 이게 자주 반복되면 props를 주고받는 양도 매우 많아지게 되어 오히려 가독성이 떨어지는 부분이 있다.
2번과 비슷한 경우로, 로직이라고 부르기 어려운 단순 UI변경에 대해 presenter 에서 변수를 선언해 컨트롤하고 싶지만 로직을 담당하는 container 파일에서 처리를 해야하는 원칙과 충돌이 일어나기 때문에 일관적으로 코드를 작성하기가 모호해진 경우가 자주 있었다. 굳이 props를 던지면서까지 작성할 로직이 아님에도 원칙을 지키기 위해 사용하는 것은 오히려 유지보수
에 악영향을 끼치는게 아닐까 싶었다.
커스텀 훅은 리액트가 기본적으로 제공해주는 훅들을(useState, useEffect 등) 이용해서 만든 함수이다.
로직은 UI를 변경시키기 위함이고, 함수형 컴포넌트에서 로직은 대부분 useState, useEffect 등의 Hook을 통해서 구현된다.
훅을 통해서 편리하기 state를 선언하고 effect를 발생시킬 수 있게 되었지만, 컴포넌트 내부에 많은 로직들이 들어가게 되면 컴포넌트가 복잡해지고, 무엇보다 동일한 로직들을 여러 컴포넌트에 걸쳐서 재사용하기 힘들다는 단점이 있었다.
일적반으로 동일한 로직이 보일경우 함수로 추출하듯이, 리액트에서도 Hook들을 이용한 동일한 로직들을 별도의 함수로 추출해서 여러 컴포넌트에 걸쳐서 사용하고자 하는 시도가 있었고 결국 커스텀 훅이란 기법을 만들게 된다.
커스텀 훅의 조건은 아래와 같다.
use
로 시작해야 한다.이후 커스텀 훅의 사용법을 익히고 적응하여 프로젝트에서 컴포넌트를 제작하기에 앞서 재사용할 수 있는 부분에 대해 먼저 고려한 후에 가능한 부분에 있어서는
Custom Hook
으로 만들어서 다른 페이지에서도 재활용할 수 있도록 만들었다. 예를 들어, 페이지네이션 로직이나, 무한스크롤, API호출로 데이터를 받아오는 로직 등 여러 부분에서 적용할 수 있었다. 이렇게 분리하다 보니 자연스럽게 관심사의 분리도 container 방식 보다 하나의 로직에 집중할 수 있어 유지보수에도 좋았다.