
이전 게시글에서 다뤘듯이, 디자인 패턴이란 소프트웨어를 개발하는 과정에서의 설계 패턴을 정의한 것이다.
프론트엔드에서도 시대가 흐르면서, 복잡한 화면 작업, React, Vue 등의 프레임워크의 도입으로
새로운 패턴 형성 및 기존 패턴의 구체적 구조화가 필요해졌다.
특히, 이러한 과정에서 어떻게 컴포넌트를 구성/활용할 것인가에 대한 고민이 깊어지고 이것이 발전해 React 디자인 패턴이 되었다.
React 디자인 패턴은
에 초점을 둔다.
이를 통해
을 이뤄낸다.
Container와 데이터를 출력하는 Presentation을 분리해 구현Presentation 컴포넌트의 재사용 가능hooks 도입 이후, 로직 분리가 이전 대비 쉬워지며 추천하지 않는 패턴이 됨즉, UI만을 구성하는 jsx와 로직을 핸들링하는 jsx를 따로 구성한다.
import {useState, useEffect} from 'react';
// Custom Hook 정의
const useFetch = (url) => {
const [data, setData] = useState(null);
useEffect(() => {
fetch(url)
.then((res) => res.json())
.then((data) => {
setData(data);
});
}, [url]);
return {data}
}
// 컴포넌트에서 사용
export default function App() {
const {data} = useFetch(`...`);
...
}
Presentation 로직과 Business 로직을 명확히 구분import React from 'react';
// HOC 함수 정의
const withLogger = (WrappedComponent) => {
return function EnhancedComponent(props) {
return <WrappedComponent {...props} />;
}
}
// 일반 컴포넌트
const NormalComponent = ({name}) => {
return <h2>HI {name}!</h2>;
}
// HOC 사용 컴포넌트
const HOCLogger = withLogger(NormalComponent);
// 활용
export default function App() {
return <HOCLogger name = "Minsu" />;
}
구조
Button, Input, Lable ... )LoginInput, LoginToggle 등)LoginComponent 등)Header + Title + LoginComponent = LoginTemplate)Props Drilling발생 가능@media, MediaQuery등 반응형 UI 구축에 어려움state)와 컨텍스트(Context) 공유import React, { createContext, useContext, useState } from 'react';
const TabsContext = createContext();
const Tabs = ({children}) => {
const [activeIdx, setActiveIdx] = useState(0);
return(
<TabsContext.Provider value={{activeIdx, setActiveIdx}}>
{children}
</TabsContext.Provider>
);
}
const TabList = ({ children }) => {
return <div>{children}</div>;
}
const Tab = ({ idx, children }) => {
const { activeIdx, setActiveIdx } = useContext(TabsContext);
return (
<button
onClick={() => setActiveIdx(idx)}
style={{ fontWeight: activeIdx === idx ? 'bold' : 'normal' }}
>
{children}
</button>
);
}
export default function App() {
return (
<Tabs>
<TabList>
<Tab index={0}>Tab 1</Tab>
<Tab index={1}>Tab 2</Tab>
</TabList>
</Tabs>
);
}
props로 전달해, 컴포넌트 내부에서 원하는 대로 렌더링할 수 있게 해줌Props로 제공된 함수에 위임하여 재사용성, 유연성 제공Presentation과 Business 로직을 분리 구성React와 Props에 대한 깊은 이해 필요Counter.jsx : Render Props 구현import React, { useState } from 'react';
export const Counter = ({render}) => {
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
return (
<div>
{render({ count, increment, decrement })}
</div>
);
}
App.jsx : 사용...
import Counter from './Counter';
export default const App = () => {
return (
<div>
<h1>Render Props Example</h1>
{/* 첫 번째 예시: 기본 카운터 */}
<Counter
render={({ count, increment, decrement }) => (
<div>
<h2>Basic Counter</h2>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
)}
/>
<hr />
{/* 두 번째 예시: 스타일이 다른 카운터 */}
<Counter
render={({ count, increment, decrement }) => (
<div>
<h2>Styled Counter</h2>
<p style={{ fontWeight: 'bold' }}>Count: {count}</p>
<button onClick={increment}>➕ Add</button>
<button onClick={decrement}>➖ Subtract</button>
</div>
)}
/>
</div>
);
}
props를 통해서만 제어, 스스로의 상태를 관리하거나 변경하지 않음(stateless)Presentation 컴포넌트, UI 렌더링btnClick 등)View - Action 연결MVC 패턴과 유사Props로만 데이터 흐름을 제어하기 때문에 Props Drilling을 야기할 수 있고, 코드가 복잡해질 수 있음단순히 이러한 디자인 패턴을 인지하는 것을 넘어,
직면한 프로젝트에 어떤 패턴이 알맞을 지를 고민하는 것이 중요하다.
예를 들어, UI의 높은 재사용성과 일관된 디자인을 위해 Atomic Design을 도입하자! 라고 할 때, props관리의 복잡성을 당연히 먼저 고려해야 한다는 것이다.
따라서 필요시엔 여러 디자인 패턴을 효율적으로 함께 사용하고,
환경에 맞는 규칙을 새롭게 만들어 적용해야한다.
또한 당연하게도 어떤 디자인 패턴을 적용하느냐에 따라
파일 구조 및 테스팅 적용이 달라지기 때문에 패키지 구조 설정 단계 ~ 테스트 단계까지 높은 관심과 고민이 필요하다.
[참고자료]