React 컴포넌트를 선언하는 방식은 두가지가 있다.
함수형(Function)과 클래스형(Class)이 있는데 이 두가지의 차이점을 비교해보며 내가 구현하고자 하는 목적에 적합한 방식으로 컴포넌트를 선언해 사용하길 바란다.
기본적으로 함수형과 클래스형의 동작은 동일하다. 하지만 클래스형 컴포넌트가 함수형보다 더 많은 기능을 제공해주는 것이 가장 큰 차이점이다.
클래스형은 state를 이용한 상태를 나타내거나 라이프사이클(LifeCycle)에 정의된 메서드를 이용해 원하는 순서에 특정한 동작도 할 수 있어 과거에는 클래스형을 많이 사용했지만 2019년 v16.8부터 함수형 컴포넌트에 리액트 훅(hook) 함께 사용할 것을 권장하고 있다.
[함수형 컴포넌트를 선호하는 이유]
클래스형 컴포넌트는 로직과 상태를 컴포넌트 내에서 구현하기 때문에 상대적으로 복잡한 UI 로직을 갖고 있는 반면, 함수형 컴포넌트는 state를 사용하지 않고 단순하게 데이터(props)를 받아서 UI에 뿌려준다.
Hook이 필요한 곳에 사용하며, 로직의 재사용이 가능하다는 장점 때문에 함수형 컴포넌트 + Hook의 사용을 선호 한다고 한다.
* 리액트 훅(React Hook)이란?
React에서 기존처럼 Class 컴포넌트를 통해 코드를 작성할 필요 없이, state와 여러 React 기능을 사용할 수 있도록 만든 라이브러리이며, 당연히 함수 컴포넌트에서난 사용이 가능하다.
함수 컴포넌트가 어떤 값을 유지할 수 있도록 만들어진 '캐시'를 이용하고자 만든 여러개의 API를 '리액트 훅' 함수라고 부른다.
컴포넌트는 단순한 템플릿 이상의 기능을 수행한다. 데이터를 받아 UI를 만들어주는 기능을 하는 것은 물론, 라이프사이클 API를 통해 컴포넌트가 화면에 나타날 때, 사라질때, 변할 때 작업들을 수행할 수 도 있다.
컴포넌트는 목적에 따라 프레젠테이션(presentation) 컴포넌트와 컨테이너(container) 컴포넌트로 나누기도 한다.
프레젠테이션 컴포넌트
- UI를 작성하여 View만 담당하는 컴포넌트이다.
- 컴포넌트 내부에 프레젠테이션 컴포넌트와 컨테이너 컴포넌트 둘 다 사용할 수 있다.
- 리덕스 스토어에 직접적으로 접근하지 않고 props만으로 데이터, 함수를 가져온다.
- 순수한 컴포넌트로 state를 사용하지 않으며, state가 있을 경우 데이터가 아닌 UI에 대한 state여야 한다.
- 주로 함수형 컴포넌트로 작성한다.
컨테이너 컴포넌트
- 다른 프레젠테이션 컴포넌트나 컨테이너 컴포넌트를 관리한다.
- 내부에 DOM 엘리먼트(UI)를 작성하지 않는다. (사용할 경우에는 감싸는 용도로)
- 스타일을 가지고 있지 않다.
- 스타일은 모두 프레젠테이션 컴포넌트 내부에 정의되어야 한다.
- 상태를 가지고 있고 리덕스에 직접 접근하여 데이터를 가져온다.
- dispatch를 하는 함수를 여기서 구한다.
차이점
1. 선언방식이 다르다.
// 클래스형 컴포넌트
// 1. class 키워드가 반드시 있어야 하며 Component로 상속을 받아야 한다.
// 2. render() 메서드가 꼭 필요하며 JSX를 반환한다.
// 3. state, lifecycle 관련 기능 사용이 가능하다.
// 4. 함수형보다 메모리 자원을 더 사용하며 임의 메서드를 정의할 수있다.
// 5. 상속보단 합성을 주로 사용한다.(class 컴포넌트를 직접 만들어서 사용하지 않는 것을 권장함)
import React, { Component } from 'react'
class App extends Component {
render() {
const name='클래스형'
return <div>{name}</div>
}
}
export default App
// 함수형 컴포넌트
// 1. state, lifeCycle 관련 기능사용 불가능하다.(Hook을 통해 해결 가능)
// 2. 메모리 자원을 함수형 컴포넌트보다 덜 사용한다.
import React from 'react'
const App = () => {
const name='함수형'
return <div>{name}</div>
}
export default App
2. State 값 사용 시 차이점
state는 컴포넌트 내부에서 바뀔 수 있는 값(객체)으로 props와 유사하지만 비공개이며, 컴포넌트에 의해 제어된다.
[함수 컴포넌트]
- useState로 state를 사용한다.
- useState를 호출하면 배열이 반환되는데 첫번째 인자는 현재 상태를 나타내며, 두번째 인자는 상태를 바꿔주는 함수를 나타낸다.
const [text, setText] = useState('');
[클래스 컴포넌트]
- constructor 안에서 this.state 초기값을 설정할 수 있다.
- constructor 없이 바로 state 초기값을 설정할 수 있다.
- state는 객체 형식이다.
- this.setState로 state 값을 변경할 수 있다.
class Example extends React.Component {
// constructor 내부 this.state 초기값 설정
constructor(props) {
super(props);
this.state = {
name: '',
nickname: ''
};
}
// constructor없이 state 초기값 설정
state = {
name: '',
nickname: ''
}
}
3. props의 사용 차이
props는 컴포넌트 속성을 설정할 때 사용하는 요소로 읽기 전용이며 컴포넌트 자체 props를 수정해서는 안되며, 모든 React 컴포넌트는 자신의 props를 다룰 때 반드시 순수함수처럼 동작해야한다. (수정되는 부분은 State!!)
[함수 컴포넌트]
const element = ({name, nickname}) => {
return (
<div>
안녕하세요. 제 이름은 {name} 입니다.
별명은 {nickname} 입니다.
</div>
);
}
props를 불러올 필요없이 바로 호출이 가능하다.
[클래스 컴포넌트]
class element extends React.Component {
render() {
const {name, nickname} = this.props;
return (
<div>
제 이름은 {name} 입니다.
별명은 {nickname} 입니다.
</div>
);
}
}
this.props로 값을 불러올 수 있다.
4. 이벤트 핸들링
[함수 컴포넌트]
- const 키워드 + 함수 형태로 선언해야 한다.
- 요소에 적용하기 위해서는 this가 필요없다.
const onClick = () => {
alert(message);
setMessage('')l
};
const onKeyPress = e => {
if(e.key === 'Enter') {
onClick();
}
};
return (
<div>
<input
type="text"
name="message"
placeholder="Type message"
value={message}
onKeyPress={onKeyPress}
/>
<button onClick={onClick}>확인</button>
</div>
[클래스 컴포넌트]
- 함수 선언 시 화살 함수로 바로 선언 가능하다.
- 요소에서 적용하기 위해서는 this를 붙여야 한다.
handleChange = e => {
this.setState({
message: e.target.value;
});
}
handleCilck () => {
alert(this.state.message);
this.setState({
message: ''
});
}
render() {
return (
<div>
<input
type="text"
name="message"
placeholder="Type a message"
value={this.state.message}
onChange={this.handleChange}
/>
<button onClick={this.handleClikc}>confirm</button>
</div>
5. LifeCycle(useEffect)
- LifeCycle API는 컴포넌트가 DOM 위에 생성되기 전 후 데이터가 변경되어 상태를 업데이트하기 전 후로 실행되는 메서드들이다.
- constructor, componentWillMount, componentDidMount 등..
클래스 컴포넌트에서는 lifecycle이 컴포넌트 중심에 맞춰져 있어 컴포넌트가 마운트되려할 때(componentWillMount), 마운트 되고나서(componentDidMount), 업데이트 되었을 때(componentDidUpdate), 언마운트(componentWillUnMount) 될때 실행했다.
함수 컴포넌트는 조금 다르다.
특정 데이터에 대해서 lifecycle이 실행된다. 데이터는 여러 개일 수 있으므로 클래스 컴포넌트에서 lifecycle을 컴포넌트 당 한번씩 사용했다면, useEffect는 데이터의 개수에 따라 여러번 사용하게 된다.
useEffect(() => {
console.log('state changed');
}, [state]);
useEffect는 컴포넌트가 첫 렌더링될 때 한 번 실행되고 그 다음부터는 해당 state가 바뀔때마다 실행된다. 즉, componentDidMount와 componentDidUpdate가 합쳐진 셈이라고 볼 수도 있다.
componentsWillUnmount의 역할도 수행할 수 있는데 return으로 함수를 제공하면 된다.
useEffect(() => {
console.log('state changed');
return () => {
console.log('state is about to change');
};
}, [state]);
데이터의 lifecycle이 하나로 합쳐진 셈이다. 이것을 활용해 setTimeout한 것을 return에서 clearTimeout 할수도 있다.
데이터가 여러개일 경우, 각각의 데이터에 useEffect를 적용하면 된다.
useEffect(() => {
console.log('state changed');
}, [state]);
useEffect(() => {
console.log('state2 changed');
}, [state2]);
componentWillMount와 componentWillUpdate는 더이상 사용하지 않으므로 useEffect에 해당하는 것은 없다.
특수한 경우!!!
마운트될 때 처음 한번만 실행하고 싶다면 빈 배열을 넣어준다. deps가 없어서 변경되는 것이 없으므로 처음 한번만 실행되고 나서 다시 재실행되지 않는다. 단, 컴포넌트가 언마운트될 때는 return의 함수가 실행된다.
useEffect(() => {
console.log('mounted');
return () => {
console.log('unmounted');
}
}, []);
반대로 컴포넌트가 리렌더링 될 때마다 실행하게 하려면 두번째 배열을 아예 안넣으면 데이터와 관련 없이 리렌더링 시마다 실행된다.
useEffect(() => {
console.log('rerended!');
});
만약, componentsDidUpdate 역할만 하고 싶다면 어떻게 할까? useEffect는 기본적으로 componentDidMount와 componentDidUpdate이 역할을 동시에 수행하므로 componentDidUpdate 역할만 수행하고 싶다면 componentDidMount의 역할을 제거(또는 무시)해야 한다.
이것은 useRef라는 훅이 필요한다. 이 부분은 다음 기회에 알아보도록 하자!!
함수에서 클래스로 변환하기
1. React.Component를 확장하는 동일한 이름의 ES6 class를 생성한다.
2. render() 메서드를 클래스 컴포넌트 내부에 생성하고 함수의 내용을 render() 메서드 안으로 옮긴다.
3. render() 내용 안에 있는 props를 this.props로 변경한다.
4. 남아있는 빈 함수 선언을 삭제한다.
[함수 컴포넌트]
const root = ReactDOM.createRoot(document.getElementById('root'));
function Clock(props) {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {props.data.toLocaleTimeString()}.</h2>
</div>
);
}
function tick() {
ReactDOM.render(
<Clock date={new Date()} />,
document.getElementById('root');
);
}
setInterval(tick.1000);
** 위의 예제는 Clock 컴포넌트에 "state"를 추가해야 한다. state는 props와 유사하지만, 비공개이며 컴포넌트에 의해 완전히 제어된다. (Clock이 타이머를 설정하고 매초 UI를 업데이트 하는 것이 Clock의 구현 세부사항이 되어야함)
[클래스 컴포넌트]
class Clock extends React.Component {
render {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString}.</h2>
</div>
);
}
}
render 메서드는 업데이트가 발생할 때마다 호출되지만, 같은 DOM 노드로 <Clock />을 렌더링할 경우 Clock 클래스의 단일 인스턴스만 사용된다. 이것은 로컬 state와 생명주기 메서드와 같은 부가적인 기능을 사용할 수 있게 해준다.
'React' 카테고리의 다른 글
[Next.js] 로컬은 폰트 로드되는데 프로덕션은 로드 안됨(Development VS Production 간단 설명) (1) | 2024.10.14 |
---|---|
web3-react 기본 개념 정리(+web3.js) (0) | 2022.09.01 |
import 중괄호 {}의 의미 (0) | 2022.08.25 |
React + Typescript (0) | 2022.05.10 |
React-Router V6로 업데이트 하면서 달라진 점 (0) | 2022.04.29 |