본문 바로가기
내마음대로만들어보자/React

[공식 문서]State와 생명주기(Lifecycle)

by 소농민! 2021. 8. 25.
728x90

시계 예시를 통해 컴포넌트를 스스로 타이머를 설정하고 매초 스스로 업데이트 하도록 만들어보자.

 

① 함수에서 클래스로 변환하기

  1. React.Component를 확장하는 동일한 이름의 ES6 class를 생성합니다.
  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>
    );
  }
}

function tick() {
  ReactDOM.render(
    <Clock date={new Date()} />,
    document.getElementById('root')
  );
}

setInterval(tick, 1000);

 

→ 이렇게 되면, Clock 은 이제 함수가 아닌 클래스로 정의된다. 


② 클래스에 로컬 state 추가하기

 

  1. render() 메서드 안에 있는 this.props.date를 this.state.date로 변경합니다.
  2. 초기 this.state를 지정하는 class constructor를 추가합니다.
  3. <Clock /> 요소에서 date prop을 삭제합니다.

 

※ 클래스 컴포넌트는 항상 props로 기본 constructor를 호출해야한다.

 

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>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);


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

컴포넌트 여러개로 늘어나다보면 컴포넌트가 삭제될때 사용 중이던 리소스를 확보하는것이 중요하다고한다.

Clock이 처음 렌더링 될떄마다 타이머를 설정 하려고하는데 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

   );

}

 

componentWillUnmount() 생명주기 메서드안에 있는 타이머를 분해해보면

 

componentWillUnmount() {

   clearInterval(this.timerID);

}

 

마지막으로 Clock 컴포넌트가 매초 작동 하도록 tick() 이라는 메서드를 구현해보자.

이것은 컴포넌트 로컬 state를 업데이트하기위해 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>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

 

메서드가 어떻게 호출되는지 간략하게 정리된 내용 참고하자.

  1. <Clock />가 ReactDOM.render()로 전달되었을 때 React는 Clock 컴포넌트의 constructor를 호출합니다. Clock이 현재 시각을 표시해야 하기 때문에 현재 시각이 포함된 객체로 this.state를 초기화합니다. 나중에 이 state를 업데이트할 것입니다.
  2. React는 Clock 컴포넌트의 render() 메서드를 호출합니다. 이를 통해 React는 화면에 표시되어야 할 내용을 알게 됩니다. 그 다음 React는 Clock의 렌더링 출력값을 일치시키기 위해 DOM을 업데이트합니다.
  3. Clock 출력값이 DOM에 삽입되면, React는 componentDidMount() 생명주기 메서드를 호출합니다. 그 안에서 Clock 컴포넌트는 매초 컴포넌트의 tick() 메서드를 호출하기 위한 타이머를 설정하도록 브라우저에 요청합니다.
  4. 매초 브라우저가 tick() 메서드를 호출합니다. 그 안에서 Clock 컴포넌트는 setState()에 현재 시각을 포함하는 객체를 호출하면서 UI 업데이트를 진행합니다. setState() 호출 덕분에 React는 state가 변경된 것을 인지하고 화면에 표시될 내용을 알아내기 위해 render() 메서드를 다시 호출합니다. 이 때 render() 메서드 안의 this.state.date가 달라지고 렌더링 출력값은 업데이트된 시각을 포함합니다. React는 이에 따라 DOM을 업데이트합니다.
  5. Clock 컴포넌트가 DOM으로부터 한 번이라도 삭제된 적이 있다면 React는 타이머를 멈추기 위해 componentWillUnmount() 생명주기 메서드를 호출합니다.

 

④ state 사용 시 주의사항

직접 state를 수정하지 말아라.

this.state 를 지정할 수 있는 유일한 공간은 constructor 이며, setState()를 통해 업데이트 해준다.

 

React는 성능을 위해 여러 setState()호출을 한꺼번에 처리할 수 있다.this.props 와 this.state 는 비동기적으로 업데이트 될 수 있다.

 

객체보다는 함수를 인자로 사용하는 다른 형태의 setState()를 사용하고 그 함수는 이전 state를 첫 번째 인자로 받아들일 것이고, 업데이트가 적용된 시점의 props를 두 번째 인자로 받아들인다. 

 

// Correct

this.setState((state, props) => ({

     counter: state.counter + props.increment

}));

 

⑤ state 업데이트 병합

setState()를 호출할 때 React는 제공한 객체를 state로 병합한다.

 

state는 다양한 독립적인 변수를 포함할 수 있는데,

constructor(props) {

   super(props);

   this.state = {

      posts: [],

      comments: []

  };

}

 

별도의 setState()호출로 이러한 변수를 독립적으로 업데이트할 수 있다. (서로에게 영향을 주지않으면서)

componentDidMount() {

    fetchPosts().then(response => {

        this.setState({

             posts: response.posts

     });

});

 

     fetchComments().then(response => {

          this.setState({

              comments: response.comments

       });

   });

}

 

다시정리하면  this.setState({comments})는 this.state.posts에 영향을 주진 않지만 this.state.comments는 완전히 대체된다.

 

⑥ 데이터는 아래로 흐른다.

state가 소유하고 설정한 컴포넌트 이외에는 어떠한 컴포넌트 외에도 접근할 수 없다.

컴포넌트는 자신의 state를 자식 컴포넌트에 props로 전달할 수 있다.

 

<FormattedDate date={this.state.date} />

 

FormattedDate 컴포넌트는 date를 자신의 props로 받을 것이고 이것이 Clock의 state로부터 왔는지, Clock의 props에서 왔는지, 수동으로 입력한 것인지 알지 못한다.

 

이를 단방향식 데이터 흐름이라고 하며, 모든 state는 특정한 컴포넌트가 소유하고 있다. 그 state로부터 파생된 UI 또는 데이터는 오직 트리 구조에서 자신의 아래에 있는 컴포넌트에만 영향을 미친다.