관심사의 분리는 프로그램을 하나의 단일 블록으로 작성하지 말고 작은 조각으로 나누어 각각 간단한 개별 작업을 완료할 수 있도록 만드는 것이다.
이는 좋은 코드를 짜기 위한 가장 기본적인 원칙이며, 더 좋은 애플리케이션을 만들기 위한 여러 디자인 패턴, 기법, 아키텍쳐 등은 결국 모두 이 SoC를 가장 기본적인 원칙으로 삼고 있습니다.
개발자는 깨끗한 코드를 작성하기 위해서 좋은 코드의 기준이 무엇인지, 그리고 방법이 무엇인지를 고민해야 한다.
그렇다면 왜 관심사를 분리해서 코드를 작성해야 할까 ?
먼저 왜 하나의 모듈은 하나의 관심사만 처리해야 하는지를 생각해보자.
하나의 목적만 가지게 된다는 것은 코드가 수정될 이유도 한가지만 존재한다는 것이다.
소프트웨어에서 기능은 수정, 확장하는 것에 유연해야 하며, 이 과정을 유지보수라고 한다.
- 변경되는 이유를 한 가지로 제한하기 위해서
- 여러 모듈들이 여러 관심사를 동시에 다루면 수정할 때 모든 모듈을 확인하며 수정해야 하는 번거롭기 때문에
단일 책임 원칙:
관심사 분리와 유사하지만, 각 모듈들이 책임을 가지고 있으며 각각 하나의 책임만을 가져야 한다는 원칙
KISS(Keep It Simple, Stupid):
모듈들은 간단하고, 단순하게 만들어야 한다는 의미.
여러 기능을 포함시키면서 복잡하게 만들면 유지보수가 힘들기 때문에, 하나의 기능만을 수행하도록 하라는 의미
React에서의 관심사는 UI와 UI를 변경시키는 부분(로직)으로 나눌 수 있다.
로직은 사용자의 입력에 반응하고 API를 호출하고 스크린이 변화하는 등 여러 동작들을 통해서 UI에 영향을 미치는 것이라고 할 수 있다.
처음엔 UI와 로직을 분리하기 위해 컴포넌트를 크게 두 계층으로 분리하는 방법인 Presentational-Container 패턴을 사용했다고 한다.
- Presentational : 로직은 상관하지 않고 UI가 어떻게 구성되어야 하는지에만 집중하는 컴포넌트
- Container : 로직들을 다루는 부분으로 UI에는 관여하지 않고 오로지 UI를 구성하고 변화하기 위한 로직에만 집중하는 컴포넌트
이렇게 컴포넌트를 두 계층으로 나누어서 Presentational을 Container로 감싼 후, 필요한 정보들과 로직을 모두 props로 전달해주는 형태로 설계하는 방법이 Presentatinal-Container 패턴이다.
하지만 아래와 같은 이유로 지금은 사용하지 않는 패턴이라고 한다..
- props를 한눈에 파악하기가 어렵다.
- 불필요한 props가 추가될 수 있다.
- 추상화 작업이 어렵다.
이러한 단점을 보완하여 더 효율적으로 관심사를 분리할 수 있는 Custom Hook이 나왔기 때문이다.
커스텀 훅은 React에서 제공해주는 혹들을 이용해서 만든 함수이다.
로직은 UI를 변경시키기 위함이고, 함수형 컴포넌트에서 로직은 대부분 useState, useEffect 등의 훅을 통해서 구현된다.
컴포넌트 내부에 로직이 많아지만 컴포넌트가 복잡해지고, 동일한 로직들을 여러 컴포넌트에 걸쳐서 재사용하기 어렵다.
그렇기 때문에 동일한 로직들을 별도의 함수로 추출해서 사용하였고, 이를 커스텀 훅이라는 기법으로 만들게 된 것이다.
커스텀 훅 조건
- React의 Hook들을 호출하는 함수여야 한다.
- 훅의 이름은 언제나
use
로 시작해야 한다.
그 다음 첫글자는 대문자여야 한다.- state 자체가 아닌 상태적인 로직(stateful logic)을 공유해야 한다.
-> 같은 Hook을 사용하는 컴포넌트들은 서로 다른 스코프를 생성하기에 완전히 독립적이다.
import { useState } from "react";
import "./styles.css";
export default function Example() {
const [isLightMode, setIsLightMode] = useState(true);
function changeMode() {
setIsLightMode((prev) => !prev);
}
return (
<>
<h1
style={{
backgroundColor: isLightMode ? "white" : "black",
color: isLightMode ? "black" : "white"
}}
>
current mode: {isLightMode ? "Light Mode" : "Dark Mode"}
</h1>
<button onClick={changeMode}>change mode</button>
</>
);
}
import { useState } from "react";
import "./styles.css";
export default function App() {
const [isLightMode, changeMode] = useToggle(true);
return (
<>
<h1
style={{
backgroundColor: isLightMode ? "white" : "black",
color: isLightMode ? "black" : "white"
}}
>
current mode: {isLightMode ? "Light Mode" : "Dark Mode"}
</h1>
<button onClick={changeMode}>change mode</button>
</>
);
}
// 관심사 분리 (파일로 따로 분리해놓는 것이 좋다)
const useToggle = (defaultValue) => {
const [toggle, setToggle] = useState(defaultValue);
const changeToggle = () => {
setToggle((prev) => !prev);
};
return [toggle, changeToggle];
};
지금은 코드가 간단하여 관심사 분리하기 전 코드가 훨씬 짧고 좋아보인다.
하지만 조금만 더 기능이 추가된다면 코드는 범잡할 수 없이 코드가 길어지고 복잡해보이기 때문에 유지보수를 위해서 관심사를 분리해야한다.
즉, 소스코드의 의존성이 추상에 의존하며 구체에는 의존하지 않아야 한다.
의존성 그리고 추상, 구체에 대한 정리는 다음 블로그에서 다뤄볼 예정이다.
공식문서 : https://react.dev/learn/reusing-logic-with-custom-hooks
공식문서 한글 번역본 : https://react-ko.dev/learn/reusing-logic-with-custom-hooks#hook-names-always-start-with-use