관심사의 분리 원칙(Seperation Of Concern)
관심사 분리 원칙이란?
코드를 유지보수함에 있어서 파악할 수 있는 단위에 한가지의 업무성격만을 유지하도록 하는 방식
코드가 단위별로 하나의 관심사에만 충실하게 만드는 것.
- 코드 파악을 위해 읽어야 하는 코드의 단위가 적음
- 수정 시 신경 써야 할 코드 수가 적어짐
- 낮은 결합도와 높은 응집도로 수정 시 변경점을 줄일 수 있고 연관 있는 코드가 모여 있음
가장 대표적인 예시로 VIEW와 BUSINESS LOGIC 간의 분리를 들 수 있음.
- VIEW 부분: BUSINESS LOGIC으로부터 데이터를 받아서 해당 데이터를 표시하는 역할만 수행
- VIEW MODEL: 해당 데이터에 대한 로직 및 관리를 맡음
1. 상태로 분리되는 경우
하나의 컴포넌트에서 같이 실행되지 않는 코드들은 따로 분리해서 사용
안좋은 예시
function ConditionComp(){
const condition = useCustomHook() === true;
useEffect(()=>{
if(condition){
return;
}
doWhenConditionTrue();
}, [condition])
return condition ? (
<Text>true</Text>
) : (
<Text>false</Text>
)
}
분리된 좋은 예시
각 컴포넌트가 실행되는 코드에 대한 내용들만 가지고 있다.
function ConditionComp(){
const condition = useCustomHook() === true;
return condition ? <TrueComponent/> : <FalseComponent/>
}
function TrueComponent(){
useEffect(()=>{
doWhenConditionTrue();
}, [condition])
return <Text>true</Text>
}
function FalseComponent(){
return <Text>false</Text>
}
2. 독립적으로 실행해도 되는 로직은 HOC, Wrapper
어던 로직이 독립적으로 실행될 때 그 로직과 결과를 사용하는 컴포넌트와 같이 사용하게 된다면 분리하는게 좋음
안 좋은 예시
function ProcessAssembleComp(){
// 사전체크로직
useComplexCheck({
onChecked: (state)=>{
if(state==="Checking"){
doProcess();
}
}
})
// processA .....
// processB .....
return <>{최종 결과}</> //
}
좋은 예시(Wrapper)
mobile인지 아니면 web인지 판단해서 Mobile의 경우 MobileComponent를 보여주고 아니면 WebComp를 보여주는 방식
사실 mobile,web구분은 서버에서 userAgent에서 파악해서 사용하므로 Wrapper로 로직을 분리해서 사용하는거에 중점으로 확인
import dynamic from 'next/dynamic';
import MobileComp from '@/components/MobileComp';
import WebComp from '@/components/WebComp';
const DeviceWrapper = dynamic(() => import('@/components/DeviceWrapper'), { ssr: false });
export default function Page() {
return (
<main>
<DeviceWrapper MobileComponent={<MobileComp />} WebComponent={<WebComp />} />
</main>
);
}
// components/DeviceWrapper.tsx
'use client';
import { useEffect, useState } from 'react';
type Props = {
MobileComponent: React.ReactNode;
WebComponent: React.ReactNode;
};
export default function DeviceWrapper({ MobileComponent, WebComponent }: Props) {
const [isMobile, setIsMobile] = useState<boolean | null>(null);
useEffect(() => {
// 사용자 에이전트를 기반으로 환경 파악 (더 정교하게 하려면 라이브러리 사용 가능)
const userAgent = navigator.userAgent.toLowerCase();
const mobile = /iphone|android|ipad|mobile/.test(userAgent);
setIsMobile(mobile);
}, []);
if (isMobile === null) return null; // 로딩 중 처리
return isMobile ? <>{MobileComponent}</> : <>{WebComponent}</>;
}
3. 커스텀 훅관리
하나의 커스텀 hook 에서는 한 관심사에 대한 내용만 참고하도록 한다.
function useUser(){
//...
}
function useTheme(){
//...
}
function useSidebar(){
//...
}
function Header() {
const user = useUser(); // 로그인 사용자만 추적
const { open, setOpen } = useSidebar(); // 사이드바 상태만 관리
return (
<div>
<span>{user.name}</span>
<button onClick={() => setOpen(!open)}>Toggle Sidebar</button>
</div>
);
}