https://reactjs.org/docs/state-and-lifecycle.html - 번역 글

이 페이지에서는 React 컴포넌트의 상태 및 수명주기 개념을 소개합니다. 자세한 컴포넌트 API 참조는 여기에서 찾을 수 있습니다 .

이전 섹션 에서 보았던 시계를 살펴봅니다.

Rendering Elements 에서는 UI를 업데이트하는 방법 중 하나만(ReactDOM.render()) 배웠습니다.

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

setInterval(tick, 1000);

CodePen

이 섹션에서는 시계 컴포넌트를 실제로 재사용가능하고 캡슐화 하는 방법을 학습합니다. 자체 타이머를 설정하고 매 초마다 업데이트합니다.

시계가 어떻게 보이는지 캡슐화하는 것부터 시작합니다.

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

CodePen

그러나 중요한 요구 사항이 빠져 있습니다. Clock 이 타이머를 설정하고 매 초 UI를 업데이트 하는 것은 Clock 의 구현 세부사항이어야 합니다.

이상적인 것은 Clock을 한번만 작성하고 자체적으로 업데이트 시키는 것입니다.

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

이를 구현하려면 Clock구성 요소에 state를 추가해야합니다 .

Stateprops와 비슷하지만 컴포넌트에 의해 완전히 제어되며 private 속성입니다.

이전에 언급 했던 대로 클래스로 정의한 컴포넌트에는 몇가지 추가 기능이 있습니다. 로컬 state는 클래스에서만 사용 가능한 기능입니다.


Converting a Function to a Class(함수를 클래스로 변환)

Clock 같은 함수형 컴포넌트를 클래스로 변환하려면 다섯 단계를 진행합니다.

  • ES6 class를 같은 이름으로 만들고, React.Componentextends합니다.
  • 빈 메서드 render()를 추가하십시오.
  • 함수 본문을 render()메서드로 옮깁니다.
  • render() 내에서 props를 this.props로 바꿉니다.
  • 남아있는 빈 함수 선언문을 제거합니다.
class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

CodePen

Clock은 이제 함수가 아닌 클래스로 정의됩니다.

render()는 업데이트가 발생할 때마다 호출되지만 <Clock />은 동일한 DOM 노드로 렌더링 하는 한 Clock클래스의 단일 인스턴스만 사용됩니다. 이를 통해 로컬 stae 및 LifeCycle 메소드와 같은 추가 기능을 사용할 수 있습니다.

이를 통해 로컬 state나 라이프사이클 훅 같은 추가 기능을 사용할 수 있습니다.


Adding Local State to a Class(Class에 로컬 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 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에 전달하는 방법을 확인하세요.
      constructor(props) {
        super(props);
        this.state = {date: new Date()};
      }
    • 클래스 컴포넌트는 항상 props 와 함께 기본 생성자를 호출합니다.
  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')
);

CodePen

다음으로, Clock에 자체 타이머를 설정하고 매 초마다 업데이트 할 것입니다.


Adding Lifecycle Methods to a Class(클래스에 라이프사이클 메서드 추가하기)

많은 컴포넌트로 구성되어있는 어플리케이션에서는 컴포넌트가 제거될 때 사용 된 리소스를 확보하는 것이 매우 중요합니다.

Clock 이 DOM에 최초로 렌더링 될 때 타이머를 설정 하려고합니다. React에서 이를 "mounting"이라고 합니다.

그리고 DOM에서 Clock을 삭제 했을때 타이머를 해제 하려고 합니다. React에서 이를 "unmounting"이라고 라고합니다.

컴포넌트가 마운트(mount) 되고 언마운트(unmount) 될 때 특정 코드를 실행하기 위해 컴포넌트 클래스에 특별한 메서드를 선언할 수 있습니다.

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

this.에 타이머 아이디를 어떻게 설정하는지 살펴봅시다.

this.props는 React 자체에 의해 설정되고 this.state는 특별한 의미가 있지만, 데이터 흐름에 사용되지 않는 항목(예: 타이머 아이디)을 저장해야 하는 경우 클래스에 수동으로 필드를 추가 할 수 있습니다.

componentWillUnmount()라이프 사이클 훅 에서 타이머를 제거합니다 .

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

마지막으로 Clock 컴포넌트에서 매 초마다 동작하는 tick() 이라는 메서드를 구현해봅시다.

this.setSTate()를 사용해서 컴포넌트 로컬 state에 대한 업데이트를 예약합니다.

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

CodePen

이제 시계는 매 초 깜빡입니다.

어떤 작업을 했는 지와 메서드가 호출되는 순서를 간단히 요약해봅시다.

  1. <Clock /> 이 ReactDOM.render() 에 전달될 때, React는 Clock 컴포넌트의 생성자 함수를 호출합니다. 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는 상태가 변경된 걸 알고 있고, render() 메서드를 다시 한번 호출해 스크린에 무엇이 있어야 하는 지 알 수 있습니다. 이번에는, render() 메서드 내의 this.state.date 가 달라지므로 렌더 출력에 업데이트된 시간이 포함됩니다. React는 그에 따라 DOM을 업데이트합니다.
  5. 만약 Clock 컴포넌트가 DOM에서 삭제되었다면, React는 componentWillUnmount() 라이프사이클 훅을 호출하고 타이머를 멈춥니다.

Using State Correctly(State 올바르게 사용하기)

setState()에 대해 알아야 할 세 가지가 있습니다.

Do Not Modify State Directly(상태를 직접 수정하지 마십시오.)

예를 들어, 다음과 같이 사용한 경우 컴포넌트를 다시 렌더링하지 않습니다.

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

대신 setState()을 사용하십시오.

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

this.state 를 지정할 수 있는 유일한 곳은 constructor(생성자 함수) 내부입니다.

State Updates May Be Asynchronous(State 업데이트는 비동기일 수 있습니다)

React는 성능 향상을 위해 여러 setState()호출을 하나의 업데이트로 배치 할 수 있습니다.

때문에 this.propsthis.state가 비동기로 업데이트될 수 있으므로, 다음 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
  };
});

State Updates are Merged(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.comments는 완전히 대체 되고, this.state.posts는 그대로 유지됩니다.


The Data Flows Down(데이터 흐름은 아래로 흐릅니다.)

부모 컴포넌트나 자식 컴포넌트는 특정 컴포넌트의 state 유무를 알 수 없으며 해당 컴포넌트가 함수나 클래스로 선언되었는지 알 수 없습니다.

부모 컴포넌트나 자식 컴포넌트는 특정 컴포넌트의 state 소유 여부를 알 수 없으며 함수 또는 클래스로 정의되는지 알 수 없습니다.

이것이 state가 종종 로컬이라 불리우거나 캡슐화 된 이유입니다. state를 소유한 컴포넌트외에는 액세스 할 수 없습니다.

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

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

이 코드는 사용자 저으이 컴포넌트에서도 동작합니다.

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

FormattedDate 컴포넌트는 propsdate를 받지만 이 값이 Clock 의 state인 지, Clockprops인 지, 혹은 입력한 값인지 알 수 없습니다.

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

CodePen

이런 데이터 흐름을 보통 “하향식(top-down)” 혹은 “단방향(unidirectional)” 데이터 흐름이라고 합니다. 모든 state는 항상 특정 컴포넌트가 가지며, 해당 state에서 파생된 모든 데이터 또는 UI는 트리의 컴포넌트 “아래(below)“에만 영향을 미칩니다.

컴포넌트 트리를 props의 폭포라고 상상해보면, 각 컴포넌트의 상태는 임의의 지점에서 추가되는 물과 비슷하지만 또한 아래로 흐릅니다.

모든 컴포넌트가 실제로 분리되어있음을 보여주기 위해, 세개의 <Clock> 을 렌더링하는 App 컴포넌트를 만들어봅시다.

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

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

CodePen

Clock 은 자체적으로 타이머를 생성하고 독립적으로 업데이트합니다.

React 앱에서 컴포넌트가 state 소유 여부는 시간이 지남에 따라 바뀔 수 있는 컴포넌트의 구현 세부 사항으로 간주합니다. state를 가진 컴포넌트 내부에서 state가 없는 컴포넌트를 사용할 수 있으며, 그 반대 경우도 마찬가지입니다.