이제까지 함수형 컴포넌트에 대해 배웠다면, class를 기반으로 컴포넌트를 생성할 수 있음에 대해 알아보자. 요즘 거의 대부분의 경우에는 함수형 컴포넌트를 사용하지만, 공부하면서 종종 마주칠 일이 있을테니 간단하게만 정리해두려 한다.
class Product extends Component {
render() {
return <h2>A Product!</h2>
}
}
리액트 16.8버전 이전까지는 함수형 컴포넌트에서는 상태 변경이 불가능했고 사이드 이팩트 또한 다룰 수 없었기 때문에 클래스 기반 컴포넌트를 사용해야 했다. 16.8 이후에 리액트에서는 리액트 훅이라는 개념을 도입했고 그 이후부터 함수형 컴포넌트를 사용하기 시작했다. 리액트 훅은 클래스 기반 컴포넌트에서는 사용이 불가능하다. 때문에 요즘 대부분의 경우에서는 함수형 컴포넌트를 사용하는 것이다.
// User.js
Const User = (props) => {
return <li className={classes.user}> {props.name} </li>;
};
export default User
- class 정의하기
- 컴포넌트에서 사용할 method 있다면 추가하기
render method
추가하기
render method
는 사용자가 정의한 것이 아닌 리액트의 필요로 인해 기본으로 내장되어 있는 메소드이다. 컴포넌트 내부에 jsx가 사용된 것을 확인하면 리액트가 자동으로 render를 호출한다. 함수형 컴포넌트에서 return의 역할과 동일하다.
Component
를 import한 후 정의한 class에 extends 하기
- props 접근 방법을
this
를 이용한 방식으로 바꾸기
// 4
import { Component } from 'react';
// 1
class User extends Component{
// 3
render() {
// 5
return <li className={classes.user}> {this.props.name} </li>;
}
}
export default User
class 기반 component
와 함수형 component
는 같이 쓰일 수 있다. 즉 하나의 프로젝트에서 어떤 파일은 class기반으로, 어떤 파일은 함수형 기반으로 작성해서 사용 가능하다. 하지만 대부분 함수형으로 쓰기 때문에 섞어 쓸 일은 많이 없다.
class 기반 component
에서는 Hook을 사용할 수 없기 때문에 따로 함수와 값을 만들어서 state관리를 해야한다. 이때class 기반 component
에서는render() 메소드
안에 함수를 추가하지 않는다. 가능은 하지만 오류가 날 수 있다. 따라서render()
외부에 함수를 정의해서 state를 관리하면 된다.
useState를 처음 호출할 때 하는 것과 동일한 작업이다.
constructor()
를 만들어서 초기화 작업을 할 수 있다. 함수형 component
에서는 state의 초기값으로 boolean값이든, string값이든 유연하게 아무 값이나 올 수 있었지만, class 기반 component
에서 state는 항상 this.state={}
를 이용하여 객체 형태로 정의된다. 이렇게 정의한 객체 내부에는 해당 component파일에서 사용할 모든 state값을 정의해서 넣어두어야 한다.
아래 코드의 toggleUsersHandler
처럼 필요할 때 상태를 업데이트하는 것을 말한다.
상태를 업데이트 하기 위해서는 this.setState()
를 호출해서 사용한다. setState()
는 Component에서 상속받은 메소드이다. 해당 메소드 내부에는 역시나 객체를 저장한다. 객체 내부에는 설정하려는 새로운 상태를 포함하여 저장한다. 내부에서 동작하는 방식은 기존의 상태를 오버라이드하지 않고 기존의 state와 결합시킨다. 이때 useState Hook에서와 같이 state값을 업데이트할 때 익명함수를 사용하여 업데이트할 수 있다.
return하는 jsx 내부에서 state를 사용하려면 this.state.stateName
으로 접근할 수 있다. 또한, 상태를 업데이트 하는 함수를 호출하기 위해서는 this.toggleUsersHandler.bind(this)
로 접근해야한다.
메소드 내부의 this 예약어가 코드가 평가될 시점의 동일한 값이나 동일한 내용을 갖도록 설정하는 자바스크립트 영역의 부분이다.
import { Component } from 'react';
class Users extends Component {
constructor() {
// class내부에서 다른 class인 constructor()를 호출하므로 super()을 사용해야 한다.
super();
this.state = {
showUsers: true,
moreState: 'Test',
};
}
toggleUsersHandler() {
this.setState((curState) => {
return { showUsers: !curState.showUsers}
});
}
render () {
return (
<div className={classes.users}>
<button onClick={toggleUsersHandler}>
{this.state.showUsers ? 'Hide' : 'Show'} Users
</button>
{this.state.showUsers && usersList}
</div>
)
}
}
컴포넌트의 생명주기란 DOM에 렌더링되거나 삭제되면서 발생한다. 이때 생명주기와 관련된 리액트 내장 메소드들이 존재한다.
1️⃣ componentDidMount()
컴포넌트가 마운트된 직후에 이 메소드를 호출한다. 즉 컴포넌트가 평가되고 DOM에 렌더링될 때를 말한다. useEffect(..., [])를 dependency없이 사용한 것과 같은 효과이다.
2️⃣ componentDidUpdate()
컴포넌트가 업데이트되면 실행된다. 상태같은 것이 변경되어 컴포넌트가 재평가되고 re-rendering될 때 실행된다. useEffect(..., [someValue])를 사용한 것과 같은 효과이다.
3️⃣ componentWillUnmount()
컴포넌트가 DOM에서 삭제되기 직전에 호출된다. useEffect의 cleanUp 함수와 같은 효과이다.
import {Component} from 'react';
class UserFinder extends Component {
constructor() {
super();
this.state = {
filteredUsers: [],
searchTerm: '',
};
}
componentDidMount() {
// http request 보내기
// 초기 한 번만 백에 요청을 보내서 받아오게 됨
this.setState( { filteredUsers: DUMMY_USERS });
}
// prevProps랑 prevState를 기본으로 받아올 수 있음. 가장 최근에 업데이트 된 값!
componentDidUpdate(prevProps, prevState) {
// 무한루프 방지 위해
// useEffect의 dependencies를 넣는 부분이랑 같은 역할
if (prevState.searchTerm !== this.state.searchTerm) {
this.setState({filteredUsers: DUMMY_USERS.filter(...생략)})
}
}
searchChangeHandler(event) {
this.setState({searchTerm: event.target.value});
}
render() {
return (
<Fragment>
<input type='search' onChange={this.searchChangeHandler.bind(this)} />
<Users users={this.state.filteredUsers} />
</Fragment>
)
}
}
context를 쓰는 방법은 함수형 component
에서 사용할 때와 거의 유사하다. 하나 다른점은 context를 불러오는 과정이다. 클래스 기반 컴포넌트
에서는 훅을 사용할 수 없기 때문에 useContext로 context를 불러올 수 없다.
✅ context가 하나인 경우
정적 property를 추가해서 사용하면 된다.
import usersContext from '../store/users-context';
import {Component} form 'react';
class userFinder extends Component {
// 정적인 값으로 context 추가하기
static contextType = usersContext;
constructor() {
super();
this.state = {
filteredUsers: [],
searchTerm: '',
}
}
componentDidMount() {
// context 사용하기
this.setState({ filteredUsers: this.context.users })
}
render() {
return (
<Fragment>
<UserContext.Consumer>
</UserContext.Consumer>
</Fragment>
)
}
}
✅ context가 여러개인 경우
정적 Property를 사용해서 해결할 수 없다. 이 경우에는 두 개의 context를 다른 하나의 component로 묶어서 사용하는 등의 방법으로 해결해야 한다. 하지만 대부분의 경우 context는 하나만 사용하므로.. 자주 쓰이진 않는다.
😡
이렇게나 불편한데 도대체 왜!!!
클래스 기반 컴포넌트
를 사용하는가.. 라고 물어본다면! 정말 대부분의 경우는함수형 컴포넌트
를 사용하지만, error handling에는클래스 컴포넌트
를 사용하는 것이 도움되기도 한다.
자식 컴포넌트
에서 발생한 에러를 부모 컴포넌트
에 전달하여 부모 컴포넌트
에서 에러 핸들링을 하는 상황을 가정해보자. 이 경우 일반적으로 자바스크립트에서 사용하는 try-catch
에러 핸들링을 사용할 수 없다. 하지만 try-catch
는 자바스크립트 코드를 이용하여 사용되어야 하는데 자식 컴포넌트
에서 부모 컴포넌트
로 에러를 전달할 경우,
jsx를 사용하여 전달되므로 try-catch
를 사용할 수 없게 되는 것이다.
이 경우에 class component
를 사용하여 error handling을 할 수 있다.
✅ 사용법
ErrorBoundary.js
파일 생성 (이름은 자유!)
class Component
생성
componentDidCatch()
메소드 추가
이 메소드는 자식 컴포넌트 중 하나가 오류를 발생시킬 때 실행된다. 이 메소드를 사용함으로써 해당 컴포넌트를 error handling component로 만들어준다. 즉 error handling component라는것은componentDidCatch()
의 life cycle method를 가지는 컴포넌트를 지칭하는 용어이다. 이러한 error handling component는 함수형 컴포넌트에서 사용할 수 없고,class Component
에서만 사용이 가능하다.
this.props.children
을 render한다.
에러를 throw하는 컴포넌트나, 에러를 handling하고 싶은 컴포넌트를error handling component
로 감싸야하므로 children을 사용한다.
import {Component} from 'react';
class ErrorBoundary extends component {
constructor() {
super();
this.state = { hasError: false };
}
// 리액트에 의해 자동으로 error가 매개변수로 전달된다
// 에러 핸들링 함수를 메소드 내부에 정의한다
componentDidCatch(error) {
this.setState({hasError: true});
}
render() {
if (this.state.hasError) {
return <p>에러 발생!</p>
}
return this.props.children;
}
}
export default ErrorBoundary;
좋은 글이에요!