클래스 기반 컴포넌트는 class 예약어를 입력하여 사용합니다.
class User {}
render() 메소드는 리액트에 필요한 특정 메소드로 리액트가 jsx 코드 안에 컴포넌트가 사용된 것을 확인하면 그때 호출하는 메소드입니다.
리액트가 render() 메소드를 호출해서 무엇이 화면에 렌더링되어야 하는지 찾습니다.
render() 메소드는 함수형 컴포넌트에서의 반환 문장과 동일합니다.
class User {
render() {
return <li className={classes.user}>{this.props.name}</li>;
}
}
클래스 기반 컴포넌트 함수 기반 컴포넌트와 다른 방식으로 props를 가져옵니다.
import { Component } from "react";
extends Component를 입력하여 Component 클래스로부터 상속을 받습니다.import { Component } from "react";
class User extends Component {}
this라는 것을 가지고 props에 접근할 수 있게 됩니다.import { Component } from "react";
class User extends Component {
render() {
return <li className={classes.user}>{this.props.name}</li>;
}
}
클래스에 생성자를 추가하고 이를 다른 클래스로 확장하게 되면 super()라는, 상위 클래스의 생성자를 호출하는 메소드를 사용해야 합니다.
import { Component } from "react";
class User extends Component {
// 생성자를 추가 했기 때문에 super() 사용
constructor() {
super();
}
render() {
return <li className={classes.user}>{this.props.name}</li>;
}
}
만약 부모 클래스의 생성자가 인자를 필요로 한다면, super()를 호출할 때 해당 인자를 전달해야 합니다.
class Parent {
// name은 John
constructor(name) {
this.name = name;
}
}
class Child extends Parent {
constructor(name, age) {
super(name); // 부모 클래스의 생성자에 인자 전달
this.age = age;
}
}
const child = new Child("John", 25);
클래스 컴포넌트에서 상태를 정의할 때는 constructor라는 것을 사용합니다.
constructor 함수는 이 구문이 인스턴스화될 때, 즉 리액트가 컴포넌트로 사용되는 클래스를 만날 때 자동적으로 호출됩니다.
class Users extends Component {
constructor() {
super();
}
}
클래스 컴포넌트에서 상태는 객체 형태입니다.
또한 "state"라는 이름의 property이어야 합니다.
class Users extends Component {
constructor() {
super();
this.state = {}
}
}
컴포넌트를 구성하는 모든 상태를 하나의 객체로 만들어야 합니다.
class Users extends Component {
constructor() {
super();
this.state = {
showUsers: true,
moreState: 'Test',
nested: {},
data: []
}
}
}
태를 변경하고자 할 때에는 보시는 것처럼 this.state에 접근해서 변경하면 안됩니다.
대신 this.setState라는 특수한 메소드를 사용합니다.
class Users extends Component {
constructor() {
super();
this.state = {
showUsers: true,
moreState: 'Test',
nested: {},
data: []
}
}
toggleUsersHandler() {
this.setState({});
}
}
this.setState는 Component를 상속받은 모든 클래스에서도 사용할 수 있습니다.
setState 항상 객체를 사용하며 설정하려는 새로운 상태를 포함합니다.
하지만 기존의 상태를 오버라이드하지 않고 병합하여 리액트가 백그라운드에서 현재 존재하는 상태와 여기에 전달하려는 객체를 결합시킵니다.
class Users extends Component {
constructor() {
super();
this.state = {
showUsers: true,
moreState: 'Test',
nested: {},
data: []
}
}
toggleUsersHandler() {
this.setState({showUsers: false});
}
}
this.setState는 갱신 함수를 지원합니다.
새로운 상태를 갖는 객체 대신 갱신 함수를 setState에 전달합니다.
만약 새로운 상태가 이전 상태에 의존한다면 이런 방법을 사용해야 다른 상태값이 손실되지 않습니다.
class Users extends Component {
constructor() {
super();
this.state = {
showUsers: true,
moreState: 'Test',
nested: {},
data: []
}
}
toggleUsersHandler() {
this.setState((currState) => {
return { showUsers: !currState.showUsers };
});
}
}
마지막으로 setState로 상태를 설정하면, 컴포넌트가 변경되고 함수가 재실행됩니다.
JavaScript에서 이벤트 핸들러 내부에서 this는 호출 문맥에 따라 달라집니다.
클래스 기반 컴포넌트에서 메서드를 이벤트 핸들러로 사용할 때, 해당 메서드를 이벤트 핸들러로 전달하면서 메서드와 메서드를 호출한 인스턴스(여기서는 컴포넌트 인스턴스) 사이의 연결이 끊어질 수 있습니다.
그 결과로 메서드 내부에서 this를 사용하면 기대한 대로 컴포넌트 자체를 가리키지 않고 undefined가 될 수 있습니다.
메소드 내부의 this 예약어가 코드가 평가될 시점의 동일한 값이나 동일한 내용을 갖도록 bind, 또는 bind(this)를 이용합니다.
class Users extends Component {
constructor() {
this.state = {
showUsers: true,
};
}
toggleUsersHandler() {
this.setState((currState) => {
return { showUsers: !currState.showUsers };
});
}
render() {
const usersList = (
<ul>
{DUMMY_USERS.map((user) => (
<User key={user.id} name={user.name} />
))}
</ul>
);
return (
<div className={classes.users}>
// this 바인딩
<button onClick={this.toggleUsersHandler.bind(this)}>
{this.state.showUsers ? "Hide" : "Show"} Users
</button>
{this.state.showUsers && usersList}
</div>
);
}
}
모든 컴포넌트는 생명 주기를 가집니다.
예를 들어, DOM에 렌더링되거나 DOM에서 삭제될 때 말이죠, 하지만 특정 메소드를 통해 생명 주기가 다른 2개의 클래스 컴포넌트를 서로 다른 시점에서 추가할 수 있습니다.
componentDidMount메서드는 컴포넌트가 마운트되어 평가되고 DOM에 렌더링될 때 호출됩니다.
useEffect를 빈 의존성 배열과 함께 추가한것과 같은 역할을 수행합니다.
"I'm rendering" 이후 "component rendered"이 출력됩니다.
class User {
componentDidMount() {
console.log("Component rendered")
}
render() {
console.log("I'm rendering")
return <li className={classes.user}>{this.props.name}</li>;
}
}
componentDidUpdate는 컴포넌트에서 무언가, 상태 같은 것이 변경되거나 하면 컴포넌트가 재평가, 리렌더링 되어 갱신되면 호출됩니다.
의존성 배열이 있는 useEffect와 동일합니다.
"I'm rendering" 이후 "component rendered"이 출력됩니다.
만약 컴포넌트가 리랜더링된다면 "I'm rendering" 이후 "I just updated"이 반복됩니다.
class User {
componentDidMount() {
console.log("Component rendered");
}
componentDidUpdate() {
console.log("I just updated");
}
render() {
console.log("I'm rendering");
return <li className={classes.user}>{this.props.name}</li>;
}
}
componentDidUpdate로 state를 변경할 경우 무한루프가 발생할 수 있습니다.
이는 인자로 주어지는 prevProps, prevState를 componentDidUpdate의 내부 조건문의 조건으로 이용해 이전 상태와 현재 상태의 비교 하여 방지할 수 있습니다.
componentDidUpdate(prevProps, prevState) {
if (prevState.searchTerm !== this.state.searchTerm) {
this.setState({
filteredUsers: DUMMY_USERS.filter((user) =>
user.name.includes(this.state.searchTerm)
),
});
}
}
컴포넌트가 DOM에서 삭제되기 직전에 호출되며 useEffect()에 있는 cleanup 함수와 같습니다.
cleanup 함수는 useEffect() 함수가 다시 실행되기 직전에 호출되며, 항상 컴포넌트가 DOM으로부터 삭제되기 전에 다시 호출됩니다.
import { Component } from "react";
import User from "./User";
class Users extends Component {
constructor() {
super();
this.state = {
showUsers: true,
};
}
toggleUsersHandler() {
this.setState((currState) => {
return { showUsers: !currState.showUsers };
});
}
render() {
const usersList = (
<ul>
{this.props.users.map((user) => (
<User key={user.id} name={user.name} />
))}
</ul>
);
return (
<div className={classes.users}>
<button onClick={this.toggleUsersHandler.bind(this)}>
{this.state.showUsers ? "Hide" : "Show"} Users
</button>
// button을 클릭하면 `User` 컴포넌트가 사라짐
{this.state.showUsers && usersList}
</div>
);
}
}
// User.js
import { Component } from "react";
class User extends Component {
// 컴포넌트가 사라질 때 "User will unmount" 출력
componentWillUnmount() {
console.log("User will unmount");
}
render() {
return <li className={classes.user}>{this.props.name}</li>;
}
}
export default User;