리액트(React)는 2013년 출시 이후 지속적으로 진화하며 다양한 컴포넌트 유형과 패턴을 선보여 왔습니다. 이 글에서는 리액트 컴포넌트의 역사와 현재 사용되는 최신 패턴을 중심으로, 각 컴포넌트 유형의 특징과 사용 여부를 상세히 분석합니다. 이 가이드를 통해, 과거와 현재의 리액트 컴포넌트 및 패턴을 명확히 구분하고, 어떤 기술이 여전히 중요한지 파악할 수 있을 것입니다.
리액트 createClass
리액트 초기에는 `createClass` 함수를 통해 컴포넌트를 정의했습니다. 이는 자바스크립트 클래스를 사용하지 않고도 리액트 컴포넌트를 생성할 수 있는 방법으로, 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;
- `createClass`는 초기 상태 설정과 렌더링 메서드를 정의하는 객체를 인자로 받습니다.
- 현재는 더 이상 권장되지 않으며, 최신 리액트 애플리케이션에서는 사용되지 않습니다.
리액트 믹스인 (패턴)
믹스인은 리액트의 초기 재사용 가능한 로직 패턴으로, 컴포넌트 간에 로직을 공유할 수 있게 해주었습니다. 하지만 여러 단점으로 인해 현재는 사용되지 않으며, 오직 `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;
- 믹스인을 사용하여 로컬 스토리지와 같은 공통 기능을 재사용할 수 있었습니다.
- 현재는 더 나은 대안인 커스텀 훅으로 대체되었습니다.
리액트 클래스 컴포넌트
2015년 ES6 도입 이후, 리액트는 클래스 기반 컴포넌트를 권장하게 되었습니다. 클래스 컴포넌트는 상태 관리와 생명주기 메서드를 사용할 수 있어 기능적으로 강력하지만, 최근에는 함수 컴포넌트와 훅(Hooks)이 더 선호됩니다.
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;
- 클래스 컴포넌트는 초기 상태 설정과 메서드 바인딩이 필요합니다.
- 리액트 훅의 도입으로 함수 컴포넌트가 업계 표준이 되면서 클래스 컴포넌트는 점차 구식으로 전락했습니다.
리액트 고차 컴포넌트 (패턴)
고차 컴포넌트(Higher-Order Components, HOCs)는 컴포넌트의 로직을 재사용하기 위한 패턴으로 인기를 끌었습니다. 그러나 현재는 커스텀 훅이 더 권장되는 방식입니다.
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);
- HOCs는 컴포넌트를 감싸서 기능을 확장합니다.
- 현재는 함수 컴포넌트와 훅을 사용하는 방식이 더 일반적입니다.
리액트 함수 컴포넌트
리액트 함수 컴포넌트는 클래스 컴포넌트를 대체하며, 훅을 통해 상태와 부수 효과를 관리할 수 있습니다. 간결하고 이해하기 쉬운 코드 구조 덕분에 현대 리액트 개발에서 표준으로 자리잡았습니다.
import { useState } from 'react';
const FunctionComponent = () => {
const [text, setText] = useState('');
const handleChangeText = event => {
setText(event.target.value);
};
return (
<div>
<p>Text: {text}</p>
<input type="text" value={text} onChange={handleChangeText} />
</div>
);
};
export default FunctionComponent;
- 함수 컴포넌트는 훅을 사용하여 상태와 부수 효과를 관리합니다.
- 커스텀 훅을 통해 로직의 재사용이 용이합니다.
리액트 서버 컴포넌트
2023년에 도입된 리액트 서버 컴포넌트(RSC)는 서버에서 컴포넌트를 실행할 수 있게 해주며, 클라이언트에 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;
- 서버 컴포넌트는 서버 측 데이터베이스와 직접 통신할 수 있습니다.
- 클라이언트 컴포넌트와 구분되며, 훅이나 이벤트 핸들러를 사용할 수 없습니다.
- Next.js와 같은 리액트 프레임워크와 함께 사용됩니다.
비동기 컴포넌트
현재 비동기 컴포넌트는 서버 컴포넌트에서만 지원되지만, 미래에는 클라이언트 컴포넌트에서도 지원될 예정입니다. 비동기 작업을 통해 데이터 페칭 등을 수행할 수 있습니다.
import { Suspense } from 'react';
const ReactServerComponent = () => {
const postsPromise = db.query('SELECT * FROM posts');
return (
<div>
<Suspense>
<ReactClientComponent promisedPosts={postsPromise} />
</Suspense>
</div>
);
};
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 };
- 비동기 컴포넌트는 데이터 페칭을 효율적으로 처리할 수 있습니다.
- 향후 클라이언트 컴포넌트에서도 지원될 예정입니다.
결론
리액트는 컴포넌트 기반 아키텍처를 통해 유연하고 재사용 가능한 UI를 구축할 수 있게 해줍니다. `createClass`와 믹스인 같은 초기 패턴은 현재 더 이상 사용되지 않으며, 클래스 컴포넌트는 함수 컴포넌트와 훅으로 대체되었습니다. 최신 리액트 애플리케이션에서는 함수 컴포넌트와 커스텀 훅을 활용하여 효율적이고 유지보수하기 쉬운 코드를 작성하는 것이 권장됩니다. 또한, 리액트 서버 컴포넌트와 비동기 컴포넌트의 도입으로 서버와 클라이언트 간의 효율적인 데이터 관리가 가능해졌습니다.
참고 자료: robinwieruch, “Types of React Components [2024]”