React - Class Component

da.circle·2023년 1월 25일
0

지금은 함수 컴포넌트가 메인이지만.. 함수 컴포넌트가 나오기 전부터 16.8버전까지 클래스 컴포넌트를 기본 컴포넌트로 사용했다고 한다.
함수 컴포넌트가 나오고 나서도 클래스 컴포넌트가 쓰였었는데, 함수 컴포넌트는 리액트 훅이 나오기 전까지 상태관리(state)를 할 수 없었기 때문이다.
후에 리액트 훅이 나오면서 함수 컴포넌트에서 state를 사용할 수 있게 되었고, 기본 컴포넌트도 사용하기 더 쉬운 함수 컴포넌트로 옮겨졌다고 한다.(훅이 나온게 16.8버전부터이다.)

왜 배워야 하는가

지금 공부하고 있는 책에 클래스 컴포넌트를 설명하는 파트가 있다.
책에서는 다음과 같이 설명하고 있다.

리액트의 버전 16.8이 나오기 전까지 긴 시간 동안 이미 많은 웹 애플리케이션과 리액트와 관련된 라이브러리들이 클래스 컴포넌트로 개발되어 왔기 때문에 우리는 클래스 컴포넌트를 다루는 방법에 대해서도 이해하고 있어야 한다.

Class Component

기본 형태

import React,{ Component } from 'react';

export class 컴포넌트명 extends Component {
  로직~
}

TypeScript의 경우

  • props와 state의 타입을 미리 지정해야 한다.
interface Props {}
interface State {}

export class 컴포넌트명 extends Component<Props, State> {
  로직~~
}

(인터페이스 명이 반드시 Props와 State일 필요 없다.. 마음대로 지으면 된다)

  • 만약 props는 없고 state만 있다면 빈 props 인터페이스를 지정해야 한다. 단순히 state만 지정하면 props로 인식하나보다.

render()

  • 화면에 컴포넌트를 표시하기 위해 사용하는 함수
  • return() 부분을 넣는다.
export class 컴포넌트명 extends Component {
  render(
    로직..
    return(jsx코드)
  )
}

Props

  • this.props를 사용해서 전달받은 Props에 접근한다.
  • 자바스크립트 구조분해할당을 통해 데이터를 할당한다.
export class 컴포넌트명 extends Component<Props> {
  render(
    const {
      props1,
      props2 = 'default value',
      props3,
    } = this.props;
	로직~~~
    return(jsx코드)
  )
}

State

  • 함수 컴포넌트에서 useState로 선언하는 state와 같다.
  • 클래스 컴포넌트에서는 state를 하나로 관리한다.

TypeScript의 경우

  • state의 타입을 지정하기 위해 interface를 만든다.
interface Props {}
interface State {
  readonly state1: string;
  readonly state2: string[];
}
  • 인터페이스 생성 후 클래스 컴포넌트에 타입을 지정한다.
export class 컴포넌트명 extends Component<Props, State> {
  로직!
}

사용방법

  • 생성자(Constructor)에서 state의 값을 초기화한다.
  • super 함수를 호출해서 전달받은 props를 부모 컴포넌트에 전달한다.(필수)
  • 생성자에서 초기화 시 값을 바로 할당할 수 있다.
constructor(props: Props) {
    super(props);
    this.state = { state1: '', state2: [] };
  }

state값 변경하기

  • state는 변하지 않는 데이터이므로 set함수로 값을 변경해야 한다.
//함수 컴포넌트의 경우
const [state, setState] = useState();
  • 클래스 컴포넌트에서도 set함수로 값을 변경한다.
  • 함수 컴포넌트처럼 set함수의 이름을 마음대로 변경할 수 없다.
  • this.setState()만을 사용한다.
//클래스 컴포넌트의 경우
this.setState({state명: 변경값})

함수

  • 클래스 함수로 정의한다.
  • public(공개) / private(비공개)를 정할 수 있다.
  • 보통은 private로 정한다.
  • Props에 있는 변수에 접근할 때는 this로 접근한다.(state도 마찬가지)
  private addToDo = (): void => {
    const { toDo, toDoList } = this.state;
    if (toDo) {
      this.setState({ toDo: '', toDoList: [...toDoList, toDo] });
    }
  };
  • 호출 역시 this를 사용한다.
<button onClick={this.함수명}/>

LifeCycle 함수

constructor()

  • state를 사용한다면 초기값 설정을 위해 사용하는 함수이다.
  • state를 사용하지 않으면 생략가능하다.
  • 반드시 super(props) 함수를 호출해서 부모 클래스의 생성자를 호출해야 한다.
  • 컴포넌트가 생성될 때 한 번만 호출된다.
  constructor(props: Props) {
    super(props);
    this.state = { toDo: '', toDoList: [] };
  }

render()

  • 클래스 컴포넌트가 렌더링되는 부분을 정의한다.
  • render함수의 반환값이 화면에 표시된다.
  • props, state값이 바뀌어서 다시 렌더링해야 할 때 마다 호출된다.
  • render함수 내에서 setState() 함수를 사용해 직접 값을 변경하게 되면 무한 루프에 빠질 수 있어서 조심해야 한다!
export class 컴포넌트명 extends Component {
  render(
    로직..
    return(jsx코드)
  )
}

getDerivedStateFromProps()

  • props로 state에 값을 설정하거나 state값이 props에 의존하여 결정될 때 사용한다.
  • state에 설정하고 싶은 값을 return하면 된다.
  • return값이 없으면 null을 return해야 한다.
  • 컴포넌트가 생성될 때 한 번만 호출된다
  static getDerivedStateFromProps(nextProps: Props, prevState: State) {
    console.log('getDerivedStateFromProps');
    return null;
  }

componentDidMount()

  • 클래스 컴포넌트가 처음 렌더링 된 후에 호출된다.
    = render함수가 처음 호출 된 후에 호출된다.
    • ajax 등 데이터를 받아오거나, 라이브러리 연동 등에 쓰인다.
    • props, state가 바뀌어도 다시 호출되지 않으므로 this.setState로 값을 직접 변경해도 된다.
    • ajax를 통해서 서버에서 받은 데이터를 state에 설정할 때 사용하면 된다.
  componentDidMount() {
    console.log('componentDidMount');
  }

shouldComponentUpdate()

  • props가 변경되거나 this.setState로 state값이 변경되면 리렌더링 하게 된다.
  • 이때 화면을 다시 렌더링하고 싶지 않으면 shouldComponentUpdate 함수를 사용한다.
shouldComponentUpdate(nextProps: Props, nextState: State){
  console.log('shouldComponentUpdate');
  return nextProps.id !== this.props.id;
  
  //리렌더링 허용
  //return true;
  
  //리렌더링 방지
  //return false;
}

리렌더링을 방지하는 이유 : 화면 렌더링을 최적화한다.

  • 리렌더링은 리액트에서 비용이 가장 많이 든다.
  • 불필요한 리렌더링을 방지하여 성능을 향상시킬 수 있다.

getSnapshotBeforeUpdate()

  • 리렌더링을 위해 render함수가 호출되어 화면이 리렌더링 되기 직전에 호출된다.
  • 반환값은 componentDidUpdate()의 매개변수로 사용된다.
  • 반환하지 않거나 getSnapshotBeforeUpdate()선언 후에 componentDidUpdate()를 선언하지 않으면 warning!
  • 많이 사용하지는 않는다고 한다. (책에선 화면을 갱신하는 동안 수동으로 스크롤 위치를 고정해야 하는 경우 등에 사용할 수 있다고 하는데.. 직접 구현을 해봐야 알 것 같다..)
  getSnapshotBeforeUpdate(prevProps: Props, prevState: State) {
    console.log('getSnapshotBeforeUpdate');
    return {
      testData: true,
    };
  }

componentDidUpdate()

  • 첫 렌더링 후에는 실행되지 않고, props, state의 변경에 의한 리렌더링때 render함수 실행 후 호출된다.
  • getSnapshotBeforeUpdate 함수와 함께 사용해서 수동으로 스크롤 위치를 고정하는 경우 등에 사용한다고 한다.(잘 사용되지 않는 함수라고 한다.)
  • state값이 바뀌면 호출되므로 직접 this.setState로 state를 변경하면 무한루프! 조심하자.
import { IScriptSnapshot } from 'typescript';

  componentDidUpdate(prevProps: Props, prevState: State, snapshot: IScriptSnapshot) {
    console.log('componentDidUpdate');
  }

componentWillUnmount()

  • 해당 컴포넌트가 화면에서 사라진 후에 호출된다.
  • componentDidMount 함수에서 연동한 라이브러리를 해제하거나, setTimeout등 타이머를 clearTimeout등으로 해지할 때 사용한다.
  • 컴포넌트가 사라진 후에 호출되므로 this.setState로 state값을 변경하려 하면 warning이 뜬다.
  componentWillUnmount() {
    console.log('componentWillUnmount');
  }

componentDidCatch()

  • render함수의 jsx에서 예외처리로 try-catch를 사용할 수 없다.
  • 예외처리에 사용하는 함수가 componentDidCatch함수이다.
  • render함수에서 return부분에 에러가 발생하면 실행된다.
  • 에러 화면을 보여주거나 자식 컴포넌트를 표시되지 않게 해서 사용자 경험을 개선할 수 있다.
  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    // this.setState({error: true});
  }

render함수는 필수이고, 나머지는 생략 가능하다.


호출 순서

컴포넌트 생성

  1. constructor
  2. getDerivedStateFromProps
  3. render
  4. componentDidMount

props 변경

  1. getDerivedStateFromProps
  2. shouldComponentUpdate
  3. render
  4. getSnapshotBeforeUpdate
  5. componentDidUpdate

state 변경

  1. shouldComponentUpdate
  2. render
  3. getSnapshotBeforeUpdate
  4. componentDidUpdate

에러 발생

  1. componentDidCatch

컴포넌트 제거

  1. componentWillUnmount

블로그에 정리하면서 느낀 점은.. 함수형 컴포넌트가 훨씬 이해하기 쉽다!
클래스형 컴포넌트를 사용해서 코드를 짠다고 생각하기 보다는 어떻게 돌아가고, 함수들이 어떤 기능을 하는지 먼저 익혀야 할 것 같다.

출처) 스무디 한 잔 마시며 끝내는 리액트+TDD

profile
프론트엔드 개발자를 꿈꾸는 사람( •̀ ω •́ )✧

0개의 댓글