JS와 React에서의 디자인 패턴을 다루기 앞서, 자바스크립트 + 리액트 디자인 패턴을 읽고,
디자인 패턴에 대한 개념을 정리해보려 한다.
디자인 패턴이란?
디자인 패턴은 소프트웨어 개발에서 반복적으로 마주치는 문제를 효율적으로 해결하기 위한 모범적인 코드 설계 방식이다.
이를 통해 개발자들은 코드의 가독성, 확장성, 유지보수성을 높일 수 있다. 특히 리액트와 같은 프레임워크에서는 상태 관리나 컴포넌트 구성에 디자인 패턴이 중요한 역할을 한다.
디자인 패턴의 필요성 및 특징
- 가독성 향상: 코드 구조가 명확해져 다른 개발자들이 코드를 이해하고 수정하기가 쉬워진다.
- 유지보수성 강화: 복잡한 로직을 반복적으로 해결할 수 있어, 코드 수정 시 더 적은 위험으로 변경이 가능하다.
- 확장성 증가: 구조화된 코드를 기반으로 새로운 기능을 추가하거나 요구사항이 변경되었을 때도 쉽게 대응할 수 있다.
- 재사용성: 검증된 해결 방법을 사용함으로써 코드 재사용성이 높아지고, 개발 시간과 비용을 줄일 수 있다.
예를 들어, 리액트에서 공급자 패턴(Provider Pattern)을 적용하면 프롭 드릴링 문제를 해결할 수 있다. Context API를 활용해 특정 컴포넌트에서 데이터를 자식 컴포넌트로 전파하지 않고도 전역 상태를 공유할 수 있어, 코드의 복잡성을 줄일 수 있다.
공급자 패턴 예제
import { createContext, useContext, useState } from 'react';
// 1. Context 생성
const ThemeContext = createContext();
// 2. 공급자 컴포넌트 정의
const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme((prevTheme) => (prevTheme === 'light' ? 'dark' : 'light'));
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
// 3. 자식 컴포넌트에서 Context 값 사용
const ThemedComponent = () => {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<div style={{ background: theme === 'light' ? '#fff' : '#333', color: theme === 'light' ? '#000' : '#fff' }}>
<p>현재 테마: {theme}</p>
<button onClick={toggleTheme}>테마 변경</button>
</div>
);
};
// 4. 최종 렌더링
const App = () => (
<ThemeProvider>
<ThemedComponent />
</ThemeProvider>
);
export default App;
이 예제에서는 ThemeProvider
를 통해 theme
상태와 toggleTheme
함수를 전역에서 공유하며, 자식 컴포넌트인 ThemedComponent
는 이를 간단히 접근할 수 있다. 이를 통해 컴포넌트 간 데이터 전파를 쉽게 관리할 수 있다.
프로토 패턴이란?
프로토 패턴은 정식으로 검증된 디자인 패턴은 아니지만, 충분한 가치와 활용성을 지닌 설계 방식을 의미한다. 디자인 패턴으로 자리 잡기 전 단계의 패턴으로, 패턴으로 인정받기 위해서는 실용성, 유용성, 그리고 적용 가능성을 보여주어야 한다.
프로토 패턴의 특징
- 패턴성 검증: 정식 패턴으로 인정받으려면 다양한 상황에서 유효성과 활용성을 보여야 한다.
- 적합성: 특정 목적에 맞는 적절한 해결 방법을 제시할 수 있어야 한다.
- 적용 가능성: 다른 프로젝트나 상황에서도 쉽게 적용될 수 있는 일반적인 해결책이어야 한다.
리액트에서 자주 활용되는 컴포지션 패턴(Composition Pattern)은 프로토 패턴으로 출발해, 컴포넌트를 재사용 가능한 구조로 묶어 유지보수를 용이하게 하고 코드 중복을 줄이는 방식으로 발전해 왔다.
컴포지션 패턴 예제
// 1. 기본적인 버튼 컴포넌트
const Button = ({ children, onClick }) => (
<button onClick={onClick}>
{children}
</button>
);
// 2. 컴포지션을 활용하여 아이콘이 있는 버튼 구현
const IconButton = ({ icon, children, onClick }) => (
<Button onClick={onClick}>
<span>{icon}</span>
{children}
</Button>
);
// 3. 다른 버튼 스타일을 쉽게 구성 가능
const SaveButton = ({ onClick }) => (
<IconButton icon="💾" onClick={onClick}>
저장
</IconButton>
);
// 4. 최종 렌더링
const App = () => (
<div>
<SaveButton onClick={() => alert('저장되었습니다!')} />
</div>
);
export default App;
패턴 구조화 및 작성
디자인 패턴 구조
디자인 패턴은 주로 특정 문제의 맥락(Context)과 집중 목표, 구성 요소 간의 관계를 중심으로 규칙적인 형태를 띤다. 패턴을 문서화하고 공유할 때 일관된 형식을 유지하는 것은 다른 개발자들이 패턴을 이해하고 활용하는 데 도움이 된다.
디자인 패턴의 주요 요소
- 이름: 패턴을 나타내는 명확하고 이해하기 쉬운 이름
- 설명: 패턴이 해결하는 문제와 이를 위한 해결 방법을 간단히 설명
- 맥락: 패턴이 적용되는 상황과 환경에 대한 개요
- 문제 정의: 해결하고자 하는 특정 문제나 도전 과제
- 해결 방법: 문제 해결을 위한 구체적인 구현 방법과 절차
리액트의 경우, 컴포넌트 컴포지션 패턴에서 ‘부모-자식 관계’를 강조하며 데이터 흐름을 최적화하는 것이 좋은 예이다. 이러한 요소들은 패턴 문서화 시 핵심적인 정보가 되어 다른 개발자가 쉽게 접근할 수 있도록 돕는다.
패턴 작성하기
디자인 패턴을 문서화하고 작성할 때는 다음과 같은 체크리스트를 고려해야 한다.
패턴 체크리스트
- 실용성: 패턴이 얼마나 실용적인지 검토합니다. 복잡한 코드보다 실용적인 코드는 유지보수가 쉽고 다양한 상황에 활용될 수 있습니다.
- 모범 사례 준수: 이미 널리 사용되는 모범 사례를 참고하면 패턴을 설계할 때 시행착오를 줄일 수 있습니다. 리액트의 HOC, Render Props 패턴은 모범 사례로 자리 잡았습니다.
- 사용자 관점 고려: 패턴은 사용자가 쉽게 이해하고 적용할 수 있어야 합니다. 과도한 추상화나 복잡한 용어는 피하고, 간단한 언어와 구체적인 예시를 통해 패턴의 이점을 전달하는 것이 중요합니다.
- 독창성에 연연하지 않기: 패턴 설계의 목적은 새로운 개념 창조가 아니라, 검증된 방법을 효율적으로 활용하는 데 있습니다. 패턴은 문제 해결의 도구일 뿐입니다.
이러한 구조와 체크리스트를 통해 디자인 패턴을 정의하고 작성함으로써,
개발 과정에서 반복되는 문제를 체계적으로 해결할 수 있다.