React 반복되는 삶 #15

CoderS·2021년 12월 20일
0

리액트 Remind

목록 보기
15/32

#15 변화를 주자!

클래스형 컴포넌트

이제는 잘 사용하지 않지만, 클래스형 컴포넌트에 대해 알아보겠다.
우리가 클래스형 컴포넌트를 배우는 이유는 나중에 이 형태를 사용하는 프로젝트를 유지보수 할 수 있고, 함수형 컴포넌트로 & Hooks 로 하지 못하는 작업이 2개정도 있다.

예전에 만들어진 리액트 관련 라이브러리는 Hooks 의 지원이 안되는 경우도 있고, react-native 관련 라우터 라이브러리인 react-native-navigation 의 경우에 클래스형 컴포넌트를 어쩔 수 없이 써야 하는 일이 종종 있다.

클래스형 컴포넌트 만드는 방법

기존에 작성했던 Hello 파일을 열어준다.

Hello.js

import React from 'react';

function Hello({ color, name, isSpecial }) {
  return (
    <div style={{ color }}>
      {isSpecial && <b>*</b>}
      안녕하세요 {name}
    </div>
  );
}

Hello.defaultProps = {
  name: '이름없음'
};

export default Hello;

위의 코드를 클래스 형태로 작성해보겠다. 다음과 같이 작성해준다.

Hello.js

import React, { Component } from 'react';

class Hello extends Component {
  render() {
    const { color, name, isSpecial } = this.props;
    return (
      <div style={{ color }}>
        {isSpecial && <b>*</b>}
        안녕하세요 {name}
      </div>
    );
  }
}

Hello.defaultProps = {
  name: '이름없음'
};

export default Hello;

클래스형 컴포넌트에서는 render( ) 메소드는 꼭 있어야 한다. 이 메소드 안에서 렌더링하고 싶은 JSX 를 반환해주면 된다. 그리고 props 를 조회할 때, this.props 를 사용해서 조회하면 된다.

다음으로 static 키워드를 선언해서, defaultProps 를 설정해준다.

Hello.js

import React, { Component } from 'react';

class Hello extends Component {
  static defaultProps = {
    name: '이름없음'
  };
  render() {
    const { color, name, isSpecial } = this.props;
    return (
      <div style={{ color }}>
        {isSpecial && <b>*</b>}
        안녕하세요 {name}
      </div>
    );
  }
}

export default Hello;

이번에는 Counter 파일을 살펴볼텐데, 함수형 컴포넌트로 작성한 형태를 보면은...

Counter.js

import React, { useReducer } from 'react';

function reducer(state, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1;
    case 'DECREMENT':
      return state - 1;
    default:
      return state;
  }
}

function Counter() {
  const [number, dispatch] = useReducer(reducer, 0);

  const onIncrease = () => {
    dispatch({ type: 'INCREMENT' });
  };

  const onDecrease = () => {
    dispatch({ type: 'DECREMENT' });
  };

  return (
    <div>
      <h1>{number}</h1>
      <button onClick={onIncrease}>+1</button>
      <button onClick={onDecrease}>-1</button>
    </div>
  );
}

export default Counter;

이제 클래스형 컴포넌트로 작성해보겠다.

Counter.js

import React, { Component } from 'react';

class Counter extends Component {
  render() {
    return (
      <div>
        <h1>0</h1>
        <button>+1</button>
        <button>-1</button>
      </div>
    );
  }
}

export default Counter;

아직 기능 구현은 하지 않았다. 일단 이 컴포넌트를 브라우저에 보여주겠다. 그리하여, index.js 를 열어서 App 대신 Counter 를 렌더링해준다.

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import Counter from './Counter';

ReactDOM.render(
  <React.StrictMode>
    <Counter />
  </React.StrictMode>,
  document.getElementById('root')
);

커스텀 메소드 만들기

이번에는 커스텀 메소드를 만드는 방법에 대해 알아보겠다. 기존에 함수형 컴포넌트에서 버튼을 클릭했을 때, 특정 작업을 실행하고 싶으면 컴포넌트 안에 밑에처럼 선언해주면 됐었다.

const onIncrease = () => {
  dispatch({ type: 'INCREMENT' });
};

클래스형 컴포넌트에서는 조금 다르다. 보통 render 함수 내부에서 선언은 할 수 있기는 있지만, 일반적으로 그렇게 하지는 않고 클래스 안에 커스텀 메서드를 선언해준다.

다음과 같이 코드를 작성해본다.

Counter.js

import React, { Component } from 'react';

class Counter extends Component {
  handleIncrease() {
    console.log('increase');
  }

  handleDecrease() {
    console.log('decrease');
  }

  render() {
    return (
      <div>
        <h1>0</h1>
        <button onClick={this.handleIncrease}>+1</button>
        <button onClick={this.handleDecrease}>-1</button>
      </div>
    );
  }
}

export default Counter;

위의 코드처럼, 클래스 컴포넌트 안에 있는 함수를 메소드라고 부른다. 보통 클래스에서 커스텀 메소드를 만들때에는 이름을 handle... 이라고 이름을 짓는다. 다만 이것은 정해진 규칙이 아니라 원하는 이름으로 지어도 상관없다.

이제 버튼을 누르면 increase 또는 decrease 라는 콘솔이 출력될 것 이다.

추후에 상태를 업데이트 할 때는 이 메소드에서 this.setState 라는 함수를 사용해야 한다.

여기서 this 는 컴포넌트를 인스턴스를 가리켜야 하는데, 현재 구현한 handleIncrease 와 handleDecrease에서는 this 를 조회하려고 하면 컴포넌트 인스턴스를 가르키지 않게 된다.

Counter.js

import React, { Component } from 'react';

class Counter extends Component {
  handleIncrease() {
    console.log('increase');
    console.log(this);
  }

  handleDecrease() {
    console.log('decrease');
  }

  render() {
    return (
      <div>
        <h1>0</h1>
        <button onClick={this.handleIncrease}>+1</button>
        <button onClick={this.handleDecrease}>-1</button>
      </div>
    );
  }
}

export default Counter;

위의 코드를 실행하고 +1 버튼을 클릭하면...

콘솔에 undefined 가 increase 와 같이 나타난다.

그 이유는, 우리가 만든 메소드들을 각 버튼들의 이벤트를 등록하게 하는 과정에서 각 메소드와 컴포넌트 인스턴스의 관계를 끊어버린 것 이다.

이 것을 해결하기 위해 3가지 방법이있다!

첫 번째는 클래스의 생성자 메소드 constructor 에서 bind 작업을 해주는 것 이다.

Counter.js

import React, { Component } from 'react';

class Counter extends Component {
  constructor(props) {
    super(props);
    this.handleIncrease = this.handleIncrease.bind(this);
    this.handleDecrease = this.handleDecrease.bind(this);
  }

  handleIncrease() {
    console.log('increase');
    console.log(this);
  }

  handleDecrease() {
    console.log('decrease');
  }

  render() {
    return (
      <div>
        <h1>0</h1>
        <button onClick={this.handleIncrease}>+1</button>
        <button onClick={this.handleDecrease}>-1</button>
      </div>
    );
  }
}

export default Counter;

이렇게 하고 다시 +1 버튼을 누르면 현재 컴포넌트 인스턴스가 잘 나타날 것 이다.

bind 메소드를 사용하면, 해당 함수에서 가르킬 this 를 직접 설정해줄 수 있다.

constructor 에서는 props 를 파라미터로 받아오고 super(props) 를 호출해주어야 하는데, 이는 클래스가 컴포넌트로서 작동 할 수 있도록 해주는 Component 쪽에 구현되어있는 생성자 함수를 먼저 실행해주고, 우리가 할 작업을 하겠다 라는 것을 의미이다.

이 방법이 일반적인 방법이고, 다음은 커스텀 메소드를 선언 할 때 화살표 함수 문법을 사용하는 것 이다.

Counter.js

import React, { Component } from 'react';

class Counter extends Component {
  handleIncrease = () => {
    console.log('increase');
    console.log(this);
  };

  handleDecrease = () => {
    console.log('decrease');
  };

  render() {
    return (
      <div>
        <h1>0</h1>
        <button onClick={this.handleIncrease}>+1</button>
        <button onClick={this.handleDecrease}>-1</button>
      </div>
    );
  }
}

export default Counter;

클래스형 컴포넌트에서 화살표 함수를 사용해서 메서드를 구현하는 것은 클래스에 특정 속성을 선언 할 수 있게 해주는 class-properties 라는 문법을 사용하는 것 이다. 하지만 이 문법은 정식 자바스크립트 문법이 아니다.

다만, CRA 로 만든 프로젝트는 적용이 되어있는 문법이기 때문에 바로 사용이 가능하다. 보통 CRA 에서 만든 프로젝트는 커스텀 메소드를 만들 때 이 방법을 많이 사용한다.

마지막으로, onClick 에서 새로운 함수를 만들어서 전달을 하는 것인데 이렇게 사용하는 것을 추천하지않는다. 렌더링 할 때마다 함수가 새로 만들어지기 때문에 나중에 컴포넌트 최적화 할 때 까다롭게 작용된다.

예 )

return (
  <div>
    <h1>0</h1>
    <button onClick={() => this.handleIncrease()}>+1</button>
    <button onClick={() => this.handleDecrease()}>-1</button>
  </div>
);

상태 선언하기

클래스형 컴포넌트에서 상태를 관리 할 때에는 state 라는 것을 사용해준다.
state 를 선언할 때는 constructor 내부에서 this.state 를 설정해주면 된다.

Counter.js

import React, { Component } from 'react';

class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = {
      counter: 0
    };
  }
  handleIncrease = () => {
    console.log('increase');
    console.log(this);
  };

  handleDecrease = () => {
    console.log('decrease');
  };

  render() {
    return (
      <div>
        <h1>{this.state.counter}</h1>
        <button onClick={this.handleIncrease}>+1</button>
        <button onClick={this.handleDecrease}>-1</button>
      </div>
    );
  }
}

export default Counter;

무조건 클래스형 컴포넌트의 state 는 객체형태여야 한다!

render 메서드에서 state 를 조회하려면 this.state 를 조회하면 된다.

그리고 우리는 화살표 함수를 사용하여 메소드를 작성 할 수 있게 해줬던
class-properties 문법이 적용되어 있다면 굳이 constructor 를 작성하지 않고도 다음과 같이 state 를 설정해줄 수 있다.

Counter.js

import React, { Component } from 'react';

class Counter extends Component {
  state = {
    counter: 0
  };
  handleIncrease = () => {
    console.log('increase');
    console.log(this);
  };

  handleDecrease = () => {
    console.log('decrease');
  };

  render() {
    return (
      <div>
        <h1>{this.state.counter}</h1>
        <button onClick={this.handleIncrease}>+1</button>
        <button onClick={this.handleDecrease}>-1</button>
      </div>
    );
  }
}

export default Counter;

CRA 로 만든 프로젝트에서는 보통 이렇게 많이 작성한다.

상태 업데이트하기

이제는 숫자를 변경하는 방법에 대해 알아보겠다.

상태를 업데이트해야 할 때에는 this.setState 함수를 사용하면 된다.

Counter.js

import React, { Component } from 'react';

class Counter extends Component {
  state = {
    counter: 0
  };
  handleIncrease = () => {
    this.setState({
      counter: this.state.counter + 1
    });
  };

  handleDecrease = () => {
    this.setState({
      counter: this.state.counter - 1
    });
  };

  render() {
    return (
      <div>
        <h1>{this.state.counter}</h1>
        <button onClick={this.handleIncrease}>+1</button>
        <button onClick={this.handleDecrease}>-1</button>
      </div>
    );
  }
}

export default Counter;

this.setState 를 사용할 때 위의 코드처럼 안에 업데이트 하고 싶은 값을 넣어서 호출해주면 된다.

만약에 다음과 같이 state 에 다른 값이 들어있다면...

import React, { Component } from 'react';

class Counter extends Component {
  state = {
    counter: 0,
    fixed: 1
  };
  handleIncrease = () => {
    this.setState({
      counter: this.state.counter + 1
    });
  };

  handleDecrease = () => {
    this.setState({
      counter: this.state.counter - 1
    });
  };

  render() {
    return (
      <div>
        <h1>{this.state.counter}</h1>
        <button onClick={this.handleIncrease}>+1</button>
        <button onClick={this.handleDecrease}>-1</button>
        <p>고정된 값: {this.state.fixed}</p>
      </div>
    );
  }
}

export default Counter;

this.setState 를 할 때 파라미터에 넣는 객체에 fixed 값을 넣어주지 않아도 값이 유지된다.

하지만 클래스형 컴포넌트의 state 에서 객체 형태의 상태를 관리해야 한다면, 불변성을 관리해주면서 업데이트를 해야 한다.

예 )

state = {
  counter: 0,
  fixed: 1,
  updateMe: {
    toggleMe: false,
    dontChangeMe: 1
  }
};

handleToggle = () => {
  this.setState({
    updateMe: {
      ...this.state.updateMe,
      toggleMe: !this.state.updateMe.toggleMe
    }
  });
};

setState 의 함수형 업데이트

이전에 배웠던 useState 에서 함수형 업데이트를 할 수 있던 것 처럼 setState 또한 함수형 업데이트를 할 수 있다.

Counter.js

import React, { Component } from 'react';

class Counter extends Component {
  state = {
    counter: 0,
    fixed: 1
  };
  handleIncrease = () => {
    this.setState(state => ({
      counter: state.counter + 1
    }));
  };

  handleDecrease = () => {
    this.setState(state => ({
      counter: state.counter - 1
    }));
  };

  render() {
    return (
      <div>
        <h1>{this.state.counter}</h1>
        <button onClick={this.handleIncrease}>+1</button>
        <button onClick={this.handleDecrease}>-1</button>
        <p>고정된 값: {this.state.fixed}</p>
      </div>
    );
  }
}

export default Counter;

함수형 업데이트는 보통 한 함수에서 setState 를 여러번에 걸쳐서 해야 되는 경우에 사용하면 유용하다.

예를 들어서 밑에 있는 코드는 setState 를 두번 사용하면서 state.counter 값에 1을 더해주는 작업을 두번하지만, 실제로 2가 더해지는 것은 아니다.

handleIncrease = () => {
  this.setState({
    counter: this.state.counter + 1
  });
  this.setState({
    counter: this.state.counter + 1
  });
};

하지만, 다음과 같이 함수형 업데이트로 처리해주면 값이 2씩 더해진다.

Counter.js

import React, { Component } from 'react';

class Counter extends Component {
  state = {
    counter: 0,
    fixed: 1
  };
  handleIncrease = () => {
    this.setState(state => ({
      counter: state.counter + 1
    }));
    this.setState(state => ({
      counter: state.counter + 1
    }));
  };

  handleDecrease = () => {
    this.setState(state => ({
      counter: state.counter - 1
    }));
  };

  render() {
    return (
      <div>
        <h1>{this.state.counter}</h1>
        <button onClick={this.handleIncrease}>+1</button>
        <button onClick={this.handleDecrease}>-1</button>
        <p>고정된 값: {this.state.fixed}</p>
      </div>
    );
  }
}

export default Counter;

+1 버튼을 두 번 눌러서 4가 나온다.

업데이트 할 객체를 넣어주는 setState 에서 2씩 더해지지 않는 이유는 setState 를 한다고 상태가 바로 바뀌는게 아니기 때문이다.

setState 는 단순히 상태를 바꾸는 함수가 아니라 상태로 바꿔달라고 요청해주는 함수로 이해를 해야한다.

그렇기 때문에 리액트에서는 상태가 바로 업데이트 되지 않고 비동기적으로 업데이트가 된다.

만약에, 상태가 업데이트 되고 나서 어떤 작업을 하고 싶다면 다음과 같이 setState 의 두번째 파라미터에 콜백함수를 넣어줄 수도 있다.

import React, { Component } from 'react';

class Counter extends Component {
  state = {
    counter: 0,
    fixed: 1
  };
  handleIncrease = () => {
    this.setState(
      {
        counter: this.state.counter + 1
      },
      () => {
        console.log(this.state.counter);
      }
    );
  };

  handleDecrease = () => {
    this.setState(state => ({
      counter: state.counter - 1
    }));
  };

  render() {
    return (
      <div>
        <h1>{this.state.counter}</h1>
        <button onClick={this.handleIncrease}>+1</button>
        <button onClick={this.handleDecrease}>-1</button>
        <p>고정된 값: {this.state.fixed}</p>
      </div>
    );
  }
}

export default Counter;

참고 : 벨로퍼트와 함께하는 모던 리액트

느낀점 :

  • 오늘은 함수형 컴포넌트가 나오기 전에 자주 사용되었던 클래스 컴포넌트에 대해 알아보았다.
  • 생각보다 예제도 많고 자세한 정보로 기재되어있어서 천천히 읽으면서 완벽하게 내 것으로 만드는 게 좋겠다.
profile
하루를 의미있게 살자!

0개의 댓글