deVSner

리액트 기초 - State와 생명주기 본문

개발 일지/react (redux, saga)

리액트 기초 - State와 생명주기

RudeofSun 2020. 4. 14. 21:22

 

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

setInterval(tick, 1000);

이번 포스팅에서는 Clock컴포넌트를 완전히 재사용하고 캡슐화하는 방법을 배울 것입니다. 이 컴포넌트는 스스로 타이머를 설정할 것이고 매초 스스로 업데이트할 것입니다.

 

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

function tick() {
  ReactDOM.render(
    <Clock date={new Date()} />,                  // 객체, 키와 값
    document.getElementById('root')
  );
} //모양이 어째 좀...?

setInterval(tick, 1000);

이상적으로 한 번만 코드를 작성하고 Clock이 스스로 업데이트하도록 만들려고 합니다.

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

이것을 구현하기 위해서 Clock 컴포넌트에 “state”를 추가해야 합니다.  

!!!! 한 번만 코드를 작성하고, Clock이 스스로 없데이트가 되게 하기 위해!!!!

 

State는 props와 유사하지만, 비공개이며 컴포넌트에 의해 완전히 제어됩니다.

 

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

다섯 단계로 Clock과 같은 함수 컴포넌트를 클래스로 변환할 수 있습니다.

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

render 메서드는 업데이트가 발생할 때마다 호출되지만, 같은 DOM 노드로 <Clock />을 렌더링하는 경우 Clock 클래스의 단일 인스턴스만 사용됩니다. 이것은 로컬 state와 생명주기 메서드와 같은 부가적인 기능을 사용할 수 있게 해줍니다.

 

 

2. 클래스에 로컬 State 추가하기

세 단계에 걸쳐서 date를 props에서 state로 이동해 보겠습니다.

1. 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>
    );
  }
}
2. 초기 this.state를 지정하는 class 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>
    );
  }
}

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

 

3. <Clock /> 요소에서 date prop을 삭제합니다.

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

 

결과

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

 

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

Clock이 처음 DOM에 렌더링 될 때마다 타이머를 설정하려고 합니다. 이것은 React에서 “마운팅”이라고 합니다.

또한 Clock에 의해 생성된 DOM이 삭제될 때마다 타이머를 해제하려고 합니다.

컴포넌트 클래스에서 특별한 메서드를 선언하여 컴포넌트가 마운트되거나 언마운트 될 때 일부 코드를 작동할 수 있습니다.

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);
  }
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() 생명주기 메서드를 호출합니다.

 

 

4. State를 올바르게 사용하기

1) 직접 State를 수정하지 마세요

// Wrong
this.state.comment = 'Hello';

// Correct
this.setState({comment: 'Hello'});


** this.state를 지정할 수 있는 유일한 공간은 바로 constructor입니다.

 

2) State 업데이트는 비동기적일 수도 있습니다.

// Wrong
this.setState({
  counter: this.state.counter + this.props.increment,
});



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

// Correct
this.setState((state, props) => ({
  counter: state.counter + props.increment
}));


// Correct
this.setState(function(state, props) {
  return {
    counter: state.counter + props.increment
  };
});

 

3) State 업데이트는 병합됩니다

setState()를 호출할 때 React는 제공한 객체를 현재 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
      });
    });
  }

 

 

 

5. 데이터는 아래로 흐릅니다

부모 컴포넌트나 자식 컴포넌트 모두 특정 컴포넌트가 유상태인지 또는 무상태인지 알 수 없고, 그들이 함수나 클래스로 정의되었는지에 대해서 관심을 가질 필요가 없습니다.

이 때문에 state는 종종 로컬 또는 캡슐화라고 불립니다. state가 소유하고 설정한 컴포넌트 이외에는 어떠한 컴포넌트에도 접근할 수 없습니다.

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

<h2>It is {this.state.date.toLocaleTimeString()}.</h2>

이것 또한 사용자 정의된 컴포넌트에도 적용 가능합니다.
<FormattedDate date={this.state.date} />

function FormattedDate(props) {
  return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}

일반적으로 이를 “하향식(top-down)” 또는 “단방향식” 데이터 흐름이라고 합니다. 모든 state는 항상 특정한 컴포넌트가 소유하고 있으며 그 state로부터 파생된 UI 또는 데이터는 오직 트리구조에서 자신의 “아래”에 있는 컴포넌트에만 영향을 미칩니다.

트리구조가 props들의 폭포라고 상상하면 각 컴포넌트의 state는 임의의 점에서 만나지만 동시에 아래로 흐르는 부가적인 수원(water source)이라고 할 수 있습니다.

모든 컴포넌트가 완전히 독립적이라는 것을 보여주기 위해 App 렌더링하는 세 개의 <Clock>을 만들었습니다.

 

function App() {
  return (
    <div>
      <Clock />
      <Clock />
      <Clock />
    </div>
  );
}

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

 Clock은 자신만의 타이머를 설정하고 독립적으로 업데이트를 합니다.