ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 리액트 주요개념 - State and Lifecycle
    IT/리액트 2022. 1. 15. 18:50
    728x90

    State란?

    동적인 데이터를 다루기 위해 사용되는 해당 컴포넌트가 다루는 값들을 의미합니다.

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

    props: properties의 줄임말로 우리가 어떠한 값을 컴포넌트에게 전달해줘야 할 때 사용함

     

    리액트 주요개념 - 엘리먼트 렌더링에서 실습했던 시계 예제를 응용해 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);

    기존 코드의 tick() 내부에서 생성하던 element 변수를 지우고 Clock이라는 새로운 컴포넌트를 생성하고 호출하였습니다.

     

    이제 여기서 Clock이 스스로 타이버를 설정하고 매초 UI를 업데이트할 수 있도록 구현할 것입니다. 그렇게 하기 위해서는 Clock 컴포넌트에 state를 추가할 것입니다.

     

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

    이제 Clock 함수를 Clock 클래스로 변환할 것입니다.

    1. React.Component를 상속받는 동일한 이름의 클래스를 생성
    2. render() 메소드 추가
    3. Clock 내용을 render() 메서드 안으로 옮김
    4. render() 내용 안에 있는 props를 this.props로 변경
    5. Clock 함수 삭제
    class Clock extends React.Component {
      render() {
        return (
          <div>
            <h1>Hello, world!</h1>
            <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
          </div>
        );
      }
    }
    render(): 컴포넌트의 기능과 모양새를 정의하는 함수로 리액트 요소를 반환

     

    클래스에 로컬 State 추가하기

    date를 props에서 state로 이동하는 과정입니다.

    1. render() 메서드 안에 있는 this.props.date를 this.state.date로 변경합니다.
      //<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
      <h2>It is {this.state.date.toLocaleTimeString()}.</h2>​
       
    2. 초기 this.state를 지정하는 class constructor를 추가합니다.
      constructor(props){
        super(props);
        this.state = {date: new Date()};
      }​
    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>
        );
      }
    }
    
    function tick() {
      ReactDOM.render(
        <Clock />, 
        document.getElementById('root')
        );
    }
    
    setInterval(tick, 1000);

     

    Lifycycle 메서드를 클래스에 추가하기

    많은 컴포넌트가 있는 애츨리케이션에서 컴포넌트가 삭제될 때 해당 컴포넌트가 사용중이던 리소스를 확보하는 것이 중요합니다.

    Clock이 처음 DOM에 렌더링 될 때마다 타이머를 설정(마운팅)하고 Clock에 의해 생성된 DOM이 삭제될 때마다 타이머를 해제(언마운팅)합니다.

    마운트(Mount): DOM 객체가 생성되고 브라우저에 나타나는 것을 의미
    언마운트(Unmount): 컴포넌트가 DOM에서 제거되는 것을 의미

     

    컴포넌트 클래스에서 특별한 메서드를 선언하여 컴포넌트가 마운트되거나 언마운트 될 때 일부 코드를 작동할 수 있는데 이러한 메서드를 생명주기 메서드라고 합니다.

     

    componentDidMount(): 컴포넌트 출력물이 DOM에 렌더링 된 후에 실행됩니다.

    componentDidMount() { //타이머 설정
      this.timerID = setInterval(
        () => this.tick(),
        1000
      );
    }

     

    componentWillUnmount(): 컴포넌트가 DOM 상에서 제거될 때에 호출됩니다.

    componentWillUnmount() { //타이머 분해
     clearInterval(this.timerID);
    }

     

    Clock 컴포넌트가 매초 작동하도록 하는 tick() 메서드를 구현합니다.

    tick() {
      this.setState({  //로컬 state 업데이트
        date: new Date()
      });
    }

    이제 class 외부에 작성된 tick() 함수와 setInterval을 제거합니다.

    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 호출 -> this.state 초기화
    2. React가 clock 컴포넌트의 render() 호출 -> DOM 업데이트
    3. clock 출력밧이 DOM에 삽입 -> componentDidMount() 호출 -> 매초 tick 메서드 호출하기 위한 타이머 설정을 브라우저에 요청
    4. 매초 브라우저가 tick 메서드 호출 -> setStae 호출 -> render 다시 호출 -> DOM 업데이트
    5. Clock 컴포넌트가 DOM으로부터 한 번이라도 삭제된 적 있다면 React는 타이머를 멈추기 위해 componentWillUnmount 호출

     

    State 올바르게 사용하기

    setState()에 대해 주의해야할 사항:

    1. 직접 State를 수정하지 않는다.
      this.state.comment = 'Hello';​
      위의 코드는 컴포넌트를 다시 렌더링하지 않기때문에 setState를 사용해야 합니다.
      this.setState({commnet: 'Hello'});​
      this.state를 지정할 수 있는 유일한 공간은 바로 constructor입니다!!
    2. State 업데이트는 비동기적일 수도 있다.React는 성능을 위해 여러 setState() 호출을 단일 업데이트로 한꺼번에 처리할 수 있습니다. this.props와 this.state가 비동기적으로 업데이트될 수 있기 때문에 다음 state를 계산할 때 해당 값에 의존해서는 안됩니다.
      this.setState({
        counter: this.state.count + this.props.increment,
      }); //카운터 업데이트 실패할 수 있음
      위의 코드는 다음과 같이 고칠 수 있습니다.
      this.setState((state, props) => ({
        counter: state.counter + props.increment
      }));
      위의 표현방식은 화살표 함수로 기존의 함수 표현식에서 function 키워드를 삭제하고 인자를 받는 매개변수의 괄호와 코드블록 사이에 화살표(=>)를 넣어 표현한 것입니다.
    3. State 업데이트는 병합된다.
      constructor(props) {
        super(props);
        this.state = {
          post: [],
          comments: []
        };
      }​
      setState를 호출할 때 React는 제공한 객체를 현재 state로 병합합니다. 위의 코드와 state는 같이 다양한 독립적인 변수를 포함할 수 있는데 각각의 setState()로 변수를 독립적으로 업데이트할 수 있습니다.

     

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

    모든 컴포넌트는 특정 컴포넌트의 존재 유무를 알 수 없고 특정 컴포넌트가 함수나 클래스로 정의되어있는지에 대해서도 관심을 가질 필요가 없습니다.

     

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

     

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

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

    FormattedDate 컴포넌트는 date를 자신의 props로 받을 것이고 이것이 Clock의 state였는지 props였는지는 알지 못합니다.

     

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

    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>
            <FormattedDate date={this.state.date} />
          </div>
        );
      }
    }
    
    function FormattedDate(props) {
      return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
    }
    
    function App() {
      return (
        <div>
          <Clock />
          <Clock />
          <Clock />
        </div>
      );
    }
    
    ReactDOM.render(
      <App />, 
      document.getElementById('root')
    );

    위의 예시에서 state의 트리구조는 다음과 같습니다.

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

    728x90

    댓글

Designed by Tistory.