React State, 생명 주기 (Life-cycle) 사용 예시

이번 글에서는 React 사용 시 가장 중요한 개념인 State, 생명 주기 (Life-cycle) 관련된 설명을 드리도록 하겠습니다. React 설치가 아직 안되신 분들은 아래 글을 먼저 참고해주시길 바랍니다.

React 프로젝트 생성 방법 (create-react-app)

일단 시계를 작동시키는 간단한 React 예제로 시작하겠습니다. 아래 코드는 보시면 쉽게 이해가 가능하실 겁니다.

const root = ReactDOM.createRoot(document.getElementById('root'));
  
function tick() {
  const element = (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2>
    </div>
  );
  root.render(element);
}

setInterval(tick, 1000);

이번 글에서는 시계 구성 요소를 진정으로 재사용하고 캡슐화하는 방법을 배웁니다. 자체 타이머를 설정하고 매초마다 업데이트합니다. 시계가 어떻게 보이는지 캡슐화하여 시작할 수 있습니다.

const root = ReactDOM.createRoot(document.getElementById('root'));

function Clock(props) {
  return (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {props.date.toLocaleTimeString()}.</h2>
    </div>
  );
}

function tick() {
  root.render(<Clock date={new Date()} />);
}

setInterval(tick, 1000);

이 코드는 그러나 중요한 요구 사항을 놓치고 있습니다. Clock이 타이머를 설정하고 매초 UI를 업데이트한다는 사실이 Clock의 구현 세부 사항이어야 합니다. 이상적으로는 이것을 한 번 작성하고 시계 자체를 업데이트하기를 원합니다.

root.render(<Clock />);

이를 구현하려면 Clock 구성 요소에 “상태”를 추가해야 합니다. 상태는 소품과 유사하지만 비공개이며 구성 요소에 의해 완전히 제어됩니다.

함수를 클래스로 변환

Clock과 같은 함수 구성 요소를 5단계로 클래스로 변환할 수 있습니다.

  1. React.Component를 확장하는 동일한 이름의 ES6 클래스를 생성합니다.
  2. render()라는 단일 빈 메서드를 추가합니다.
  3. 함수 본문을 render() 메서드로 이동합니다.
  4. render() 본문에서 props를 this.props로 바꿉니다.
  5. 나머지 빈 함수 선언을 삭제합니다.
class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

시계는 이제 함수가 아닌 클래스로 정의됩니다. render 메서드는 업데이트가 발생할 때마다 호출되지만 <Clock />을 동일한 DOM 노드로 렌더링하는 한 Clock 클래스의 단일 인스턴스만 사용됩니다. 이를 통해 로컬 상태 및 수명 주기 메서드와 같은 추가 기능을 사용할 수 있습니다.

클래스에 로컬 상태 추가

이제 날짜를 props에서 state로 옮길 것입니다. render() 메서드에서 this.props.date를 this.state.date로 바꿉니다.

class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

초기 this.state를 할당하는 클래스 생성자를 추가합니다.

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

기본 생성자에 props를 전달하는 방법에 유의하세요.

 constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

클래스 구성 요소는 항상 props와 함께 기본 생성자를 호출해야 합니다. <Clock /> 요소에서 날짜 속성을 제거합니다.

root.render(<Clock />);

나중에 타이머 코드를 구성 요소 자체에 다시 추가할 겁니다. 결과는 다음과 같습니다.

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Clock />);

클래스에 생명 주기 메서드 추가

구성 요소가 많은 응용 프로그램에서는 구성 요소가 파괴될 때 해당 구성 요소가 차지하는 리소스를 확보하는 것이 매우 중요합니다. Clock이 처음으로 DOM에 렌더링될 때마다 타이머를 설정하려고 합니다. 이를 React에서는 “마운팅”이라고 합니다.

또한 Clock에 의해 생성된 DOM이 제거될 때마다 해당 타이머를 지우고 싶습니다. 이것은 React에서 “마운트 해제”라고 합니다. 구성 요소가 마운트 및 마운트 해제될 때 일부 코드를 실행하기 위해 구성 요소 클래스에서 특수 메서드를 선언할 수 있습니다.

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
  }

  componentWillUnmount() {
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

이러한 메소드를 “라이프사이클 메소드”라고 합니다. componentDidMount() 메서드는 구성 요소 출력이 DOM에 렌더링된 후에 실행됩니다. 다음은 타이머를 설정하기에 좋은 위치입니다.

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

타이머 ID를 this(this.timerID)에 저장하는 방법에 유의하세요. this.props는 React 자체에 의해 설정되고 this.state는 특별한 의미가 있지만 데이터 흐름에 참여하지 않는 항목(예: 타이머 ID)을 저장해야 하는 경우 클래스에 수동으로 필드를 추가할 수 있습니다. componentWillUnmount() 수명 주기 메서드에서 타이머를 해제합니다.

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

마지막으로 Clock 구성 요소가 매초 실행되는 tick()이라는 메서드를 구현합니다. this.setState()를 사용하여 구성 요소 로컬 상태에 대한 업데이트를 예약합니다.

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Clock />);
error: Content is protected !!