Skip to content
himprover
GitHubVelog

[번역] React의 디자인 패턴

Translate, React8 min read

banner

이 게시물은 원본 아티클인 React Design Patterns 를 한글로 번역한 게시글입니다. 게시물 내용의 저작권은 원작자 Anuradha Aggarwal 에게 있습니다.

안녕하세요!! React에서 개발하는 동안 여러분이 이전에 접근했던 방식으로 쉽게 해결되지 않는 경우가 있었을 것입니다.

각각의 경우들은 최적화된 방식으로 문제를 해결하고, 코드를 확장 가능하게 만들기 위해 다른 패턴을 요구합니다.

React는 일반적인 문제들을 해결할 수 있는 다른 디자인 패턴들을 제공합니다. 일반적으로 React에서 사용하는 디자인 패턴은 아래와 같습니다.

  • Presentation & Container Component
  • Higher Order Components
  • Render Props
  • Compound Components
  • Hooks

한번 하나하나 알아보도록 하겠습니다.

🎯 1. Presentational & Container Component

React에서 각각의 컴포넌트는 두 개의 부분을 가지고 있습니다. 첫번째는 데이터를 처리하는 로직이고, 나머지 하나는 데이터를 화면에 보여주는 것 입니다.

이 패턴은 컴포넌트를 두개의 카테고리로 분리해 관심사 분리를 도와줍니다. Container 컴포넌트는 로직을 처리하고 상태를 관리합니다. Presentational 컴포넌트는 제공받은 속성(props)으로 UI를 렌더링 하는 것에 집중합니다.

export const DisplayList = ({ patientRecord }) => {
return (
<ul>
{patientRecord.map((patient, id) => (
<li key={id}>{patient}</li>
))}
</ul>
);
};

반면에 Container 컴포넌트는 내부 상태와 데이터 페칭 로직을 추적하고 비즈니스 로직을 처리합니다.

export const PatientComponent = () => {
const [patientList, getPatientList] = React.useState([]);
React.useEffect(async () => {
// API로 데이터를 가져오고 비즈니스 로직을 적용
const data = await fetchPatientList();
setPatientList([...data]);
}, []);
// presentational 컴포넌트를 보여주기 위해 데이터 제공
return <DisplayList patientRecord={patientList} />;
};

Container 컴포넌트는 상태와 필요한 콜백 함수를 Presentational 컴포넌트에 속성으로 전달합니다. Presentational 컴포넌트는 데이터를 가져오는 것과 비즈니스 로직에 대한 걱정 없이, 제공받은 속성 값으로 UI를 보여주는 것에만 책임을 집니다.

🎯 2. Higher Order Components

HOC는 하나의 컴포넌트를 입력으로 받고, 추가 기능이 포함된 새로운 컴포넌트를 출력으로 제공합니다. 이는 추가 기능과 함께 컴포넌트를 감싸 컴포넌트 로직을 재사용 할 수 있게 해줍니다.

HOC를 사용하면 코드를 복사하지 않고 공통 기능을 보다 쉽게 여러 컴포넌트에 추가할 수 있습니다.

import React from 'react';
const withLogger = (WrappedComponent) => {
class WithLogger extends React.Component {
componentDidMount() {
console.log(`Component ${WrappedComponent.name} mounted.`);
}
componentWillUnmount() {
console.log(`Component ${WrappedComponent.name} will unmount.`);
}
render() {
return <wrappedComponent {...this.props} />;
}
}
return WithLogger;
};
// 사용 부분
const MyComponent = (props) => {
return <div>My Component</div>;
};
const EnhancedComponent = withLogger(MyComponent);

위 예제에서 withLogger는 컴포넌트를 입력받아(WrappedComponent) 새로운 컴포넌트를 반환하는(WithLogger) HOC 입니다. 반환된 컴포넌트는 마운트되고 해제될때 콘솔에 메세지를 로깅합니다.

HOC를 사용하면 기존 컴포넌트는 MyComponentwithLogger로 감싸고, 새로운 EnhancedComponent 컴포넌트를 만듭니다. Enhanced 컴포넌트는 MyComponent와 동일한 기능을 가지지만, HOC에서 정의한 로깅 기능이 포함되어 있습니다.

🎯 3. Render props

이 패턴에서 컴포넌트는 속성으로 함수를 전달받고 이 함수를 사용해 컨텐츠를 렌더링합니다.

import React from 'react';
class MouseTracker extends React.Component {
state = { x: 0, y: 0 };
handleMouseMove = (event) => {
this.setState({ x: event.clientX, y: event.clientY });
};
render() {
return (
<div onMouseMove={this.handleMouseMove}>
{this.props.renderPosition(this.state)}
</div>
);
}
}
// 사용 부분
const DisplayMousePosition = () => (
<MouseTracker
renderPosition={({ x, y }) => (
<div>
Mouse position: {x}, {y}
</div>
)}
/>
);

renderPosition 같은 속성(props)을 render props라고 부릅니다. 왜냐면 이 속성이 UI의 일부를 렌더링 하는 방법을 지정하기 때문입니다.

🎯 4. Compound Components

Compound 컴포넌트 패턴은 React에서 여러개의 관련 컴포넌트를 그룹으로 만들어, 사용자(개발자)가 이를 수정하고 렌더링을 제어할 수 있도록 해줍니다.

이는 자식 컴포넌트의 동작과 상태를 캡슐화하는 부모 컴포넌트에 정의하게 해줍니다. 그와 동시에 사용자가 자식 컴포넌트의 구조와 외관을 정할 수 있는 유연성을 제공합니다.

React 리스트 컴포넌트를 만들어야 하는 상황을 생각해보겠습니다. 이 컴포넌트는 입란 HTML의 순서 없는 리스트 또는 아코디언 컴포넌트와 비슷한 모양을 가져야 합니다.

<List>
<ListItem>1</ListItem>
<ListItem>2</ListItem>
<ListItem>3</ListItem>
<ListItem>4</ListItem>
</List>

이 시나리오에서 우리는 리스트컴포넌트가 리스트아이템컴포넌트와 같은 자식 컴포넌트에게 상태를 전달해야 합니다. 여기서는 context APIReact.cloneElement API를 사용해 구현할 수 있씁니다.

같은 시나리오에서 Context API를 사용한 예제를 알아보도록 하겠습니다.

const ListContext = React.createContext();
// 부모 컴포넌트
export const List = (props) => {
const { children, size } = props;
const { Provider } = ListContext;
const sharedProp = { size };
return (
<Provider value={sharedProp}>
<ul>{children}</ul>
</Provider>
);
};
// 자식 컴포넌트
export const ListItem = (props) => {
const contextProp = React.useContext(ListContext);
const { size } = contextProp;
return (
<li className={size === 'medium' ? 'bg-primary' : 'bg-secondary'}>
{props.children}
</li>
);
};
// 사용 부분
<List size='medium'>
<ListItem>Item 1</ListItem>
<ListItem>Item 2</ListItem>
<ListItem>Item 3</ListItem>
<ListItem>Item 4</ListItem>
</List>;

위 예제에서 React Context는 부모(<List>)와 자식(<ListItem>) 사이에서 size 속성을 공유하고 있습니다. 또한 사용자는 각각의 리스트 아이템에 유연하게 커스텀한 컨텐츠를 제공할 수 있습니다.

🎯 5. Hooks

Hook은 React가 클래스 컴포넌트에서 함수형 컴포넌트로 전환 될 때 소개되었습니다. React에서는 상태를 관리하기 위해 다양한 hook과 생명 주기 메서드가 제공됩니다.

import React, { useState } from 'react';
const Counter = () => {
const [count, setCount] = useState(0);
return <Button onClick={() => setCount(count + 1)}></Button>;
};

또한 커스텀 hook을 만들어 우리의 특정 상황을 해결하고 코드를 재사용할 수 있습니다.

일반적으로 많이 사용되는 react 내장 hook은 여기에서 확인할 수 있습니다.