티스토리 뷰

React.JS

[React.JS] State와 Lifecycle

버미노트 2017. 4. 6. 12:38


React.JS



State와 Liftcycle



State의 필요성

시계 컴포넌트를 만들어 봅시다. 시계 컴포넌트를 만들려면, 매초마다 화면을 갱신해야 시간이 흐르는 것처럼 보이겠죠?

function Clock(props) {
  return (
    <h1>{props.date.toLocaleTimeString()}</h1>  
  );
}

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

setInterval(tick, 1000);

CodePen으로 예제 확인하기


화면을 다시 그려주는 방법으로, setInterval()를 사용하여 ReactDOM.render()을 매초 호출하여 화면을 갱신하였습니다.


매초 화면을 갱신하는 기능은 Clock 컴포넌트 안에서 구현하는 것이 코드가 훨씬 간결해 보일 것입니다. 화면을 매초 갱신하는 기능을 Clock 컴포넌트 안에서 구현하기 위해 사용하는 것이 state입니다. 컴포넌트에서 유동적인 데이터를 다를 때는 state를 사용합니다.



Class Component

state를 사용하기 위해서 먼저, function으로 구현된 컴포넌트를 class로 구현해야 합니다.

class Clock extends React.Component {
  render() {
    return (
      <h1>{this.props.date.toLocaleTimeString()}</h1>  
    );
  }
}

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

setInterval(tick, 1000);

CodePen으로 예제 확인하기


function으로 구현된 컴포넌트를 class로 구현하는 방법은 위의 코드와 같습니다.

  • 1번 줄, function Clock(props) -> class Clock extends React.Component 로 변경, class는 React.Component를 상속 받아야 합니다.
  • 2~3번 줄, return -> render() { return ; } 으로 변경, React.Component의 render() 함수를 오버라이드하여, render() 함수 내에서 JSX를 return 해야 합니다.
  • 4번 줄, props.date -> this.props.date 로 변경, function의 인자로 넘겨 받은 props를 this.props로 변경해야 합니다.



State

Class Componenet로 변경이 끝나면, props로 정의된 date를 state로 변경하는 작업이 필요합니다.

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }
  
  render() {
    return (
      <h1>{this.state.date.toLocaleTimeString()}</h1>  
    );
  }
}

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

setInterval(tick, 1000);

CodePen으로 예제 확인하기


props를 state로 변경한 코드는 위와 같습니다.


  • 2~5번 줄, constructor(생성자)에서 this.state로 state를 정의합니다, 이 때 super(props) 로 부모 클래스에 props를 항상 넘겨줘야 합니다. 파생된 클래스에서 this를 사용하기 전에 반드시 super을 사용해야 합니다. 그렇지 않으면 참조 에러가 발생합니다. ([자바스크립트] OOP, ES6(ECMA Srcipt 6) - 클래스(Class)에 생성자(Constructor) 참고)
  • 9번 줄, this.props.date -> this.state.date 로 변경, constructor에서 정의한 state를 사용하기 위해 this.state로 변경합니다.
  • 16번 줄, date={new Date()} 제거, constructor에서 정의한 state를 사용하기 때문에, props를 제거합니다.


위의 코드의 실행 결과를 보면, setInterval이 실행 되어도, 시간이 가지 않는 것을 확인 할 수 있습니다. setInterval로 ReactDOM.render()가 실행되어도, props로 현재 시간이 넘겨지지 않기 때문에, 시간이 변경되지 않습니다.

이제 마지막으로 매초마다 state를 변경하는 기능만 추가되면 시계 컴포넌트가 완성됩니다.



Lifecycle

Clock 컴포넌트가 DOM에 랜더링 될 때(마운팅-mounting), 타이머를 설정하여 시간이 흐르도록 해야 합니다. 또한 Clock 컴포넌트가 DOM에서 제거 될 때(언마운팅-unmounting) 타이머를 중지 시켜 쓸데없는 자원 낭비를 막아야 합니다.

React에서 마운팅, 언마운팅 될 경우, 특정 코드를 수행 할 수 있는 메소드를 제공합니다. 이러한 메소드들을 lifycycle hooks라고 부릅니다.

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 (
      <h1>{this.state.date.toLocaleTimeString()}</h1>
    );
  }
}

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

CodePen으로 예제 확인하기


  • 7~12번 줄, Clock 컴포넌트가 마운팅 될 때, setInterval로 매초 마다 tick 메소드를 실행시킵니다. React.Component의 componentDidMount 메소드를 오버라이드 하여 구현합니다.
  • 8번 줄, setInterval()을 사용하여 tick 메소드를 실행 시킬 때 화살표 함수를 사용하였습니다(setInterval(this.tick, 1000)이 아닌). setInterval()에서 일반함수를 호출할 경우, this가 바인딩되어 this가 window 객체가 되기 때문입니다. 그렇게 되면 tick 메소드 안의 this.setState를 사용할 수 없게 됩니다. (화살표 함수의 바인딩 관련 내용은 [자바스크립트] ES6(ECMA Script 6) - 화살표 함수(Arrow function) 참고 바랍니다.)
  • 14~16번 줄, Clock 컴포넌트가 언마운팅 될 때, clearInterval()로 매초 마다 tick 메소드가 실행되는 것을 종료합니다. React.Component의 componentWillUnMount 메소드를 오버라이드 하여 구현합니다.


컴포넌트들이 언마운팅 될 때, 사용된 자원들을 확보하는 것은 매우 중요합니다. DOM에 컴포넌트가 마운팅 되었을 때, 자원을 할당하고 컴포넌트가 언마운팅 됬을 때 자원을 해제 하는 것이 가능해 집니다.



state 바르게 사용하기

state를 직접 변경하면 안됨

아래와 같이 state를 직접 변경할 경우 DOM을 다시 랜더링하지 않습니다.

this.state.sayHello = "Hello!"; // DOM을 다시 랜더링 하지 않습니다.

setState()를 사용하여 state를 변경 해야, state가 변경 될 때 DOM을 다시 랜더링하여 화면에 출력하게 됩니다.

this.setState({ sayHello: "Hello!" }) // 반드시 setState로 state를 변경해야 합니다.


state의 값은 비동기로 업데이트 됨

React는 setState 성능 향상을 위해 단일 업데이트 될 수 있고, this.props와 this.state는 비동기로 값이 업데이트 될 수 있습니다.

그렇기 때문에 아래와 같이 setState를 사용하면 원하는 값을 보장 받지 못할 수 있습니다.

this.setState({
  counter: this.state.counter + this.props.increment // 값을 보장 받지 못합니다.
});

이런 문제를 해결하는 방법은 아래와 같습니다. setState에 리터럴 object를 넘기는 방법이 아닌 function을 넘기는 방법입니다.

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

첫번째 인자값으로 이전 state를 두번째 인자값으로 props를 넘겨 받습니다. 화살표 함수를 사용하지 않고 일반 함수로도 구현 가능합니다.

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


state의 값은 병합 됨

setState로 state의 값을 변경할 경우, setState로 넘긴 값만 병합되어 업데이트 됩니다.

constructor(props) {
  super(props);
  this.state = {
    first: "",
    second: ""
  };
}

componentDidMount() {
  this.setState({
    first: "1"  // state.first만 병합되며 업데이드 됨
  });

  this.setState({
    second: "2"  // state.second만 병합되며 업데이드 됨
  });
}



Data Flows

state는 특정 컴포넌트가 소유하고 있어야 하며, state의 data 흐름은 하위로 향해야 합니다. 이것을 top-down 혹은 unidirectional data flow라고 합니다.

예를 들면,

function FormattedDate(props) {
  return <h1>Korea Time : {props.date.toLocaleTimeString()}</h1>
}

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 (
      <FormattedDate date={this.state.date} />
    );
  }
}

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

CodePen으로 예제 확인하기


Clock는 상위 컴포넌트이고, FormattedDate는 하위 컴포넌트 입니다.

date라는 state는 Clock 컴포넌트만 소유하고 있고, date를 하위 컴포넌트인 FormattedDate 컴포넌트로 흘러 보내고 있습니다.

즉, date라는 state를 소유하고 있는 상위 Clock 컴포넌트에서 하위 FormattedDate 컴포넌트로 data가 흘러갑니다.


여러개의 상위 컴포넌트와 하위 컴포넌트로 구성된 어플리케이션에서도 동일합니다.




참고 - https://facebook.github.io/react/docs/state-and-lifecycle.html


'React.JS' 카테고리의 다른 글

[React.JS] List와 Key  (0) 2017.04.18
[React.JS] Handling Events  (0) 2017.04.07
[React.JS] Componenst와 Props  (0) 2017.04.02
[React.JS] JSX  (0) 2017.04.02
[React.JS] CodePen, create-react-app으로 React.JS 개발하기  (0) 2017.04.01
댓글
공지사항
최근에 올라온 글