728x90
개요
- 2013년 리액트가 출시된 후 2024년 까지 다양한 유형의 컴포넌트가 등장해왔다.
- 그 중 일부는 deprecated 기술이 되었지만, 왜 이전 기술이 사용하지 않게 되었는지, 최신 컴포넌트와 패턴은 무엇인지 알아보자
컴포넌트 유형과 패턴
- 리액트 createClass - deprecated
- 리액트 믹스인 (패턴) - deprecated
- 리액트 클래스 컴포넌트 - 권장하지 않음
- 리액트 고차 컴포넌트 (패턴) - 권장하지 않음
- 리액트 함수 컴포넌트
- Presentational & Container Component (패턴)
- Custom Hook (패턴)
- Atomic Design (패턴)
- View Asset Component (패턴)
- 리액트 서버 컴포넌트
- 비동기 컴포넌트
리액트 createClass
- 자바스크립트 클래스를 사용하지 않고도, 리액트 클래스 컴포넌트를 생성할 수 있는 팩토리 함수이다.
- ES5에는 클래스 문법이 없었기에 2015년 ES6가 도입되기 전까지 리액트 표준 컴포넌트였다.
- 현재는 리액트 코어 패키지에 저장되지 않으며, create-react-class라는 추가 노드 패키지를 설치해야 한다.
import createClass from 'create-react-class';
const CreateClassComponent = createClass({
getInitialState: function() {
return {
text: '',
};
},
handleChangeText: function(event) {
this.setState({ text: event.target.value });
},
render: function() {
return (
<div>
<p>Text: {this.state.text}</p>
<input
type="text"
value={this.state.text}
onChange={this.handleChangeText}
/>
</div>
);
},
});
export default CreateClassComponent;
리액트 믹스인 (패턴)
- 재사용 가능한 컴포넌트 로직을 위한 리액트의 첫 번째 패턴이다.
- mixins을 사용하여 컴포넌트 로직을 독립된 객체로 추출할 수 있었다.
- 오직 createClass 컴포넌트에서만 사용되었다.
import createClass from 'create-react-class';
const LocalStorageMixin = {
getInitialState: function() {
return {
text: localStorage.getItem('text') || '',
};
},
componentDidUpdate: function() {
localStorage.setItem('text', this.state.text);
},
};
const CreateClassWithMixinComponent = createClass({
mixins: [LocalStorageMixin],
handleChangeText: function(event) {
this.setState({ text: event.target.value });
},
render: function() {
return (
<div>
<p>Text: {this.state.text}</p>
<input
type="text"
value={this.state.text}
onChange={this.handleChangeText}
/>
</div>
);
},
});
export default CreateClassWithMixinComponent;
리액트 클래스 컴포넌트
- 리액트 클래스 컴포넌트는 ES6가 출시된 이후 자바스크립트 클래스 문법을 활용할 수 있는 방법으로 도입된 컴포넌트이다.
- 리액트 클래스 컴포넌트는 컴포넌트의 마운팅, 업데이트, 언마운팅을 위한 다양한 생명주기 메서드도 제공합니다.
- 2015년 3월에 출시된 후에도 createClass 함수를 더 많이 사용하였지만 2017년 4월에 버전 15.5에서 리액트가 createClass를 폐지하고 클래스 컴포넌트를 대신 사용할 것을 권장하며 사용하게 되었다.
- 컴포넌트 내부 로직은 객체 지향의 상속을 제공되었고, 상속 그 이상의 로직은 리액트 합성(composition)을 목적으로 사용하는 것이 권장되었다.
- 2019년 2월에 출시된 버전 16.8전까지 함수 컴포넌트가 상태를 관리하거나 부수 효과를 처리할 수 없었기 때문에 클래스 컴포넌트와 함수 컴포넌트가 공존하여 사용했다.
import React from 'react';
class ClassComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
text: '',
};
this.handleChangeText = this.handleChangeText.bind(this);
}
handleChangeText(event) {
this.setState({ text: event.target.value });
}
render() {
return (
<div>
<p>Text: {this.state.text}</p>
<input
type="text"
value={this.state.text}
onChange={this.handleChangeText}
/>
</div>
);
}
}
export default ClassComponent;
리액트 고차 컴포넌트 (패턴)
- 리액트 컴포넌트 간의 재사용 가능한 로직을 위한 고급 패턴이다.
- 컴포넌트를 입력으로 받아 확장된 기능을 가진 컴포넌트를 출력하는 컴포넌트를 의미한다.
리액트 Render Prop 컴포넌트 (패턴)
- 고차 컴포넌트의 대안으로 사용되는 React 컴포넌트 간에 코드를 공유하기 위해 함수 props를 이용하는 패턴
리액트 고차 컴포넌트와 Render Prop 컴포넌트 모두 현재의 리액트 애플리케이션에서는 많이 사용되지 않으며 오늘날에는 리액트 훅과 함께 사용하는 함수 컴포넌트가 컴포넌트 간 로직을 공유하는 표준으로 알려져 있다.
import React from 'react';
const withLocalStorage = storageKey => Component => {
return class extends React.Component {
constructor(props) {
super(props);
this.state = {
value: localStorage.getItem(storageKey) || '',
};
}
componentDidUpdate() {
localStorage.setItem(storageKey, this.state.value);
}
onChangeValue = event => {
this.setState({ value: event.target.value });
};
render() {
return (
<Component
value={this.state.value}
onChangeValue={this.onChangeValue}
{...this.props}
/>
);
}
};
};
class ClassComponent extends React.Component {
render() {
return (
<div>
<p>Text: {this.props.value}</p>
<input
type="text"
value={this.props.value}
onChange={this.props.onChangeValue}
/>
</div>
);
}
}
export default withLocalStorage('text')(ClassComponent);
리액트 함수 컴포넌트
- 함수로 표현되는 리액트 클래스 컴포넌트를 대체하는 용도로 사용되는 컴포넌트이다.
- 리액트 훅이 2019년 2월에 출시된 버전 16.8에서 도입되면서 이제는 상태와 부수 효과를 사용할 수 있어 함수 컴포넌트가 업계 표준이 되었다.
- FC, 때때로 함수형 컴포넌트라고도 불린다.
- 리액트에는 다양한 내장 훅들이 있으며, 커스텀 훅을 만들 수 있다.
import { useEffect, useState } from 'react';
const FunctionComponent = () => {
const [text, setText] = useState(localStorage.getItem('text') || '');
useEffect(() => {
localStorage.setItem('text', text);
}, [text]);
const handleChangeText = event => {
setText(event.target.value);
};
return (
<div>
<p>Text: {text}</p>
<input type="text" value={text} onChange={handleChangeText} />
</div>
);
};
export default FunctionComponent;
Presentational & Container Component (패턴)
- 2015년 Dan Abramov가 처음 소개한 패턴으로, 가장 기본적이고 유명한 패턴
- Presentational Component
- 화면에 표시하는 것만 담당. props를 통해서 데이터나 콜백을 받아옴
- UI와 관련된 상태만 가짐 (대표 예시: dropdown 열림 여부)
- Container Component
- 동작, data fetch 등의 business logic만을 담당
- Presentational Component에 보여줄 데이터를 가져오거나, 변화시키거나, 행동/동작 등을 정의
- DOM Markup이나 스타일(css) 없이, 연관이 있는 서브 컴포넌트를 렌더링
- stateful한 경향
components
├── Login
│ ├── LoginContainer.tsx
│ ├── LoginPresentation.tsx
│ └── Login.style.ts
└── User
├── UserContainer.tsx
├── UserPresentation.tsx
└── User.style.ts
...
Component - Custom Hook (패턴)
- hooks의 도입 이후, Dan Abramov가 새롭게 제안한 방식
// before
function TotalComponent() {
const [titleState, setTitleState] = useState("some title");
const data = fetchSomething();
const onClose = () => {
// do something
}
// ...
return (
<div>
<div>** This component only awares about UI</div>
<div>{titleState}</div>
<div>{data.description}</div>
<button onClick={onClose}>Close</button>
{* ... *}
</div>
)
}
// after
function useTitle() {
const [title, setTitle] = useState("some title");
const data = fetchSomething();
const onClose = () => {
// do something
};
// ...
return { title, setTitle, data, onClose };
}
function TotalComponent() {
const { title, setTitle, data, onClose } = useTitle();
return (
<div>
<div>** This component only awares about UI</div>
<div>{titleState}</div>
<div>{data.description}</div>
<button onClick={onClose}>Close</button>
{* ... *}
</div>
)
}
Atomic Design (패턴)
- 2013년 Brad Frost에 의해 처음으로 제시되었다. 원래는 디자인 시스템을 위한 패턴
- Atoms
- 가장 작은 단위의 구성 요소. 본인 자체의 스타일만 가지고 있어야 함.
- 예: input, label, button 등
- Molecules
- atoms가 모여서 만들어지는 하나의 구성 요소
- 실제로 무언가 동작을 할 수 있도록 만든 의미있는 단위.
- 예: label과 input 2개를 합쳐 아이디와 패스워드를 입력할 수 있는 LoginInput을 만듦
- Organisms
- 서로 동일하거나 다른 molecules, atoms로 구성한 비교적 복잡한 구조
- 예: LoginInput, LoginStatusToggle, button을 합쳐 LoginComponent를 만듦
- Templates
- 페이지의 기본 구조 및 스타일링에 집중한 단위
- 예: Header - Title Logo - Component - Advertise Banner - Footer 구조로 LoginTemplate을 만듦
- Pages
- template을 사용하여 실제 페이지를 구성
- 어플리케이션의 상태 관리가 이루어져야 함.
- 컴포넌트를 동작시키기 위한 상태 관리는 atom, molecule 등의 하위 단계에서 이루어져도 됨
- 예: input의 onChange를 위한 상태 등은 input atom에서
- 예: 실제로 input을 통해 로그인하는 action은 page에서
components
├── ui
│ ├── atoms // 가장 작은 단위의 컴포넌트
│ │ ├── Input
│ │ └── Checkbox
│ ├── molecules // atom을 여러 개 조합한 컴포넌트
│ │ └── LoginInputs
│ └── organisms // molecule과 atom을 조합하여 만든 컴포넌트
│ └── LoginComponent
├── templates // 컴포넌트를 넣어 사용할 레이아웃
│ ├── LoginTemplate
│ ├── UserTemplate
│ └── BookTemplate
└── pages // 가장 큰 단위의, templates에 atom, molecule, organism 등을 주입한 컴포넌트
└── Login
- Atom - Block - Page
components
├── atoms // 가장 작은 단위의 컴포넌트
│ ├── Input
│ └── Checkbox
├── blocks // 페이지보다 작은 단위면서 공통적으로 사용할 수 있는 컴포넌트
│ └── LoginInputs
└── pages
└── Login
VAC (패턴)
- View Asset Component
- 기존의 View 컴포넌트에서 jsx와 관련된 영역을 props object로 추상화한 뒤, VAC로 분리해서 개발하는 방식
// before
function NumberBox() {
const [value, setValue] = useState(0);
return (
<div>
<button disabled={value < 1} onClick={() => setValue(value - 1)}>-</button>
<span>{value}</span>
<button disabled={value > 9} onClick={() => setValue(value + 1)}>+</button>
</div>
);
}
// after
function NumberBox() {
const [value, setValue] = useState(0);
const props = {
value,
disabledDecrease: value < 1,
disabledIncrease: value > 9,
onDecrease: () => setValue(value - 1),
onIncrease: () => setValue(value + 1),
};
// JSX 대신 VAC
return <NumberBoxView {...props} />;
}
function NumberBoxView= ({ value, disabledDecrease, disabledIncrease, onIncrease, onDecrease }) => (
<div>
<button disabled={disabledDecrease} onClick={onDecrease}>-</button>
<span>{value}</span>
<button disabled={disabledIncrease} onClick={onIncrease}>+</button>
</div>
);
리액트 서버 컴포넌트 (RSC)
- 2023년에 리액트에 추가된 컴포넌트이다.
- 클라이언트에 오직 HTML만 전송되며, 개발자가 컴포넌트를 서버에서 실행하여 리소스에 접근할 수 있게 해준다.
- 서버에서 실행되기 때문에 클라이언트 컴포넌트와 달리 리액트 훅이나 이벤트 핸들러와 같은 자바스크립트를 사용할 수 없다.
- 이를 구현하기 위해서는 리액트 프레임워크(예: Next.js)가 필요하다.
// 서버 측 리소스(예: 데이터베이스)에서 데이터를 가져와 JSX를 렌더링 된 HTML로 클라이언트에 전송하는 서버 컴포넌트의 예
const ReactServerComponent = async () => {
const posts = await db.query('SELECT * FROM posts');
return (
<div>
<ul>
{posts?.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
};
export default ReactServerComponent;
비동기 컴포넌트
- 컴포넌트가 async로 표시되면 비동기 작업(예: 데이터 페칭)을 수행할 수 있는 컴포넌트이다.
- 현재 비동기 컴포넌트는 서버 컴포넌트에서만 지원되지만, 미래에는 클라이언트 컴포넌트에서도 지원될 예정이다.
import { Suspense } from 'react';
const ReactServerComponent = () => {
const postsPromise = db.query('SELECT * FROM posts');
return (
<div>
<Suspense>
<ReactClientComponent promisedPosts={postsPromise} />
</Suspense>
</div>
);
};
'use client';
import { use } from 'react';
const ReactClientComponent = ({ promisedPosts }) => {
const posts = use(promisedPosts);
return (
<ul>
{posts?.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
};
export { ReactClientComponent };
https://velog.io/@aborile/React-Design-Pattern#presentational--container-component-pattern
https://ykss.netlify.app/translation/types_of_react_components/?utm_source=substack&utm_medium=email
728x90