이전 게시글에서 다뤘듯이, 디자인 패턴이란 소프트웨어를 개발하는 과정에서의 설계 패턴을 정의한 것이다.
프론트엔드에서도 시대가 흐르면서, 복잡한 화면 작업, 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
관리의 복잡성을 당연히 먼저 고려해야 한다는 것이다.
따라서 필요시엔 여러 디자인 패턴을 효율적으로 함께 사용하고,
환경에 맞는 규칙을 새롭게 만들어 적용해야한다.
또한 당연하게도 어떤 디자인 패턴을 적용하느냐에 따라
파일 구조 및 테스팅 적용이 달라지기 때문에 패키지 구조 설정 단계 ~ 테스트 단계까지 높은 관심과 고민이 필요하다.
[참고자료]