본문 바로가기
Language/React

[React 공식 Doc 가이드 #6] State and Lifecycle

by ocwokocw 2021. 2. 11.

- 이 글은 React 공식 홈페이지 Docs v16.8.3 (https://reactjs.org/docs) 에 기반하여 작성한 글입니다.

- Clock 예제 다시 살펴보기

이전예제에서 1초 마다 시간을 보여주던 예제를 기억하는가? Rendering Element 에서 UI를 업데이트 하기 위해 ReactDOM.render()를 호출하였다.

이번 Section 에서는 실질적으로 재사용할 수 있고 은닉화(API 를 만듦에 있어서 필요한 정보외에는 client가 알아야 되는 부분을 최소화 하는)된 Clock component 를 만들어 볼것이다. 기능은 이전 예제와 똑같지만 Component 자체에서 timer를 설정하고, 갱신되게 만들어보자.

 

일단 여태까지 Component를 정의하고 사용해봤으니 Clock component를 만들면서 은닉화를 시도해보자.



여태까지 배운 개념을 이용해서 function Clock을 이용해서 component를 정의하였고, props에서 date를 참조하였으며, element를 return 하였다. tick() 에서는 ReactDOM.render에 Clock compoent를 전달하였다. 

 

끝이라고 생각할 수도 있지만 Timer 를 설정하고 UI 를 update 하는 부분은 Clock component 의 내부 구현에 있는게 나을 것이다. Clock component 를 써야하는 사용자라고 가정해보자. Clock component 를 사용할 때 어떤 특별한 설정을 하지 않았다면 기본적으로 현재시간을 표시한다고 생각할 것이다.

 

그런데 굳이 현재시간 date 를 attribute 로 넘겨야 하는걸 알아야 하며, 심지어는 1초마다 동작하도록 타이머세팅을 해야한다. 이런 타이머를 Clock Component 외에 다른곳에서는 사용하지도 않는데 말이다. 

 

결론적으로는 아래와 같은 코드가 되는게 이상적이다.

 

이런 코드가 가능하려면 props 와 비슷하지만 private 하면서 Component 안에서 제어를 할 수 있는 "state" 를 사용해야 한다.


이전 Section 에서 Component 를 function이나 class 둘 중 하나로 정의할 수 있다고 했고, class 를 이용할 때 추가적인  특징이 있다고 했다. 바로 이 Local state가 class 를 이용해 component를 정의할 때만 사용가능한 특징이다.


- Converting a Function to a Class

보통 function을 class 로 변경할 때는 5단계의 절차를 거친다.

1. React.Component 를 확장한 ES6 class 를 만든다.(이름은 같아야 한다.)

2. 빈 render() 함수를 만든다.

3. function component 의 body를 class 의 render 함수 body로 옮긴다.

4. class의 render body 에서 props를 this.props 로 변경

5. 기존 정의된 function component 삭제


Clock component를 class 방식으로 재정의 하였다. 이제 Local state와 lifecycle 함수들을 적용시켜 보자.


- Class에 Local state 추가

아래 3단계에 걸쳐서 props의 date를 state로 옮겨보자.

1. render() 함수안의 this.props.date를 this.state.date 로 변경

2. 생성자(class constructor) 에서 this.state 초기화

3. <Clock /> element 에서 date attribute를 삭제, 최종적으로 아래와 같은 코드가 된다.

생성자에서 props를 Base 생성자의 넘겨준 부분(super(props)) 부분은 눈여겨 볼만하다. Java와 비슷하지 않은가? Class component는 언제나 Base 생성자에 props를 넘겨 주어야 한다.

 

오류는 없지만 최초 생성자에서만 new Date() 연산을 했기 때문에 최초 생성시간만 출력하고 변경되지 않는다. 이제 <Clock /> Component에 타이머를 설정하고, 매 초마다 시간이 갱신되어 출력되게 바꿔보자.


- Class 에서 Lifecycle 함수 사용하기

예제에서야 Component를 많이 생성하지 않지만 실제 많은 Component 를 이용해서 application 을 만들경우, 그 Component가 destroy(Spring 프레임워크를 사용해봤다면 이 메소드 이름이 친숙할 것이다.) 될 경우에 리소스를 해제하는 작업은 중요하다. 초기화 작업을 설정하고 리소스 해제 작업을 가능하게 해주는 lifecycle method 를 소개한다.

 

Clock이 DOM 에 최초 rendering 될 때 마다 timer가 세팅되어야 할 것이다. 이 과정을 React 에서는 "mounting" 이라고 한다.


또한 Clock 에 의해 만들어진 DOM 이 삭제될때마다 timer는 해제 되어야 할 것이다. 이 과정을 React 에서는 "unmounting" 이라고 한다.

 

Component 가 mount, unmount 될 때, 특정한 동작을 수행하기 위해서는 특별한 함수들을 정의 해야 한다.

위의 componentDidMount, componentWillUnmount 같은 메소드들이 특별한 함수들("lifecycle methods") 이다.
componentDidMount 는 component가 DOM 에 rendering 되고 난 직후 수행된다. component 에 timer를 할당한다면 여기에 하는게 좋겠다.

위의 메소드는 그냥 동작이 되니까 그냥 넘어갈수도 있지만, 뭔가 이상한 부분이 있다. this.timerID 부분을 유심히 보자. 지금까지 props와 state만을 이용해서 데이터를 저장 및 처리했는데, this.props나 this.state 없이 곧바로 this.timerID 에 Interval를 해제할 수 있는 ID를 할당하였다.

 

React 에 의해서 세팅되는 this.props 나 특별한 의미를 갖는 this.state 와 대조적으로 만약 timerID 같이 데이터의 흐름과 상관없는 변수가 있다면 class에 곧바로 추가적인 field를 선언할 수 있다.

 

이제 timer를 해제하기 위해 componentWillUnmount 이름을 가진 life cycle 메소드를 사용해보자.

그리고 Clock component 에서 매 초마다 동작을 수행하는(state의 date를 참조하기로 했으므로 이제 tick은 state의 date만 갱신할 것이다.) 메소드를 정의해보자. Component의 local state를 갱신하기 위해 this.setState()를 사용해야 한다. 

 

최종적으로 아래와 같은 코드가 된다.

이제 매초마다 갱신되는 Clock component를 완성하였다. 전체적인 동작을 살펴보자.
1. 우선 <Clock /> component가 ReactDOM.render() 에 전달되었다. 이때 Clock component의 생성자가 호출된다. Clock은 현재시간을 보여줘야 하기 때문에, 현재시간을 this.state 에 표현하여 초기화 한다. 물론 this.state의 시간정보는 이후에 계속 갱신할 것이다.

 

2. React는 Clock component의 render 메소드를 호출한다. 이 때 React는 DOM 에 해당 component를 어떻게 그려야 할지 비로소 알게 된다. Clock의 render 메소드를 호출한 결과값과 대상 DOM을 비교하여 DOM을 "효율적으로" 갱신한다.

 

3. Clock의 render 메소드를 호출한 결과가 DOM에 rendering 될 때, componentDidMount() ("lifecycle method") 가 호출된다. 이때 Clock은 브라우저에게 1초마다 tick 함수를 호출하라고 요청한다.

 

4. 매초마다 브라우저는 tick 함수를 호출한다. tick 함수 안에서 호출하는 setState 함수 덕분에 React는 Clock component의 state가 변경되었다는 것을 감지하고, 화면에 출력하기 위해 render() 함수를 재 호출한다. 이 때 render 함수안에서의 this.state.date 가 다르기 때문에 React는 DOM을 update 한다.

 

5. 만약 Clock component가 DOM에서 지워지면 componentWillUnmount() ("lifecycle method") 가 호출되고 componentDidMount에서 정의한 timer를 정지한다.


- State를 주의해서 사용하자

1. State를 직접 갱신하지 마라.

 

만약 this.setState 부분을 this.state 으로 바로 참조하여 새 변수를 할당하면 논리적으로는 동작하는게 이상이 없지만 화면의 초가 바뀌지 않는다. this.state는 constructor 에서만 초기화할 수 있다.

<setState 를 사용하여 갱신할 때 DOM 이 Clock의 render() 함수에 의해 정상적으로 갱신되는 모습>



<this.state 를 사용하면 DOM 이 정상적으로 갱신되지 않는다.>


2. State의 갱신은 비동기적으로 동작한다.

 

React는 속도향상을 위해서 여러개의 setState를 한꺼번에 처리한다. 이 때문에 this.props나 this.state의 갱신이 비동기적으로 일어나므로 다음상태를 계산하기 위해서 this.props나 this.state의 값에 의존하지 마라.

아래와 같은 코드를 짜면 기대하는대로 동작하지 않는다.

우리는 여태까지 setState에 Object 형태로만 넘겼다. 앞에서 말했듯이 this.props나 this.state의 갱신은 비동기적으로 일어나므로 위의 코드는 우리가 생각하는 것처럼 동작하지 않는다.

우리가 생각하는것처럼 동작하게 하려면 this.setState에 object대신 function을 넘겨야 한다. 이 function은 1번째 인자로 이전 상태의 state, 2번째 인자로 update가 적용된 props 가 넘어온다.

위에서는 ES6인 arrow function을 사용했지만, 기존문법도 동작한다. this 와 같은 context 에 의존적인 문법을 사용한 것이 아니라 parameter 를 사용했기 때문에 context에 영향을 받지 않기 때문이다.


3. setState 를 이용하여 state 를 갱신할 때, 데이터는 기존의 state 와 병합된다.

 

setState() 를 호출할 때, React 는 object 를 현재의 state 와 병합한다.

예를 들어 state 에 아래와 같은 변수들이 있다고 가정하자.

this.state에 posts와 comment 를 한꺼번에 할당할 필요 없이, setState 각 변수를 할당하면 React 가 알아서 병합한다.

this.setState({comments}) 로 state 데이터를 변경할 때 this.state.posts 는 손상되지 않고, this.state.comments 만 대체된다.


- Date Flows Down

Parent나 Child component 들은 어떤 component 가 stateful 하거나 stateless 한지, 그리고 그 Component가 function 또는 Class 로 정의되어 있는지 알 수 없다. 

 

state 를 지역적이라거나(local) 또는 캡슐화(encapsulated) 하는 이유가 여기에 있다. Component 는 자신의 state 를 Child component 에게 props 로 전달할 수 있다.

 

아래의 이전의 Clock component 에서 시간을 출력했던 <h2>It is {this.state.date.toLocaleTimeString()}.</h2> 이 부분을 Component 화 해서 FormattedDate 라는 Component 를 만들었다고 가정하자.

 

Clock 에서 FormattedDate component 를 참조할 때 다음과 같은 형태가 될 것이다.

FormattedDate component 는 props 에서 date 를 참조하였다. 그리고 그 date 가 props 에 전달되어서 올 때 Clock의 state 데이터로 부터 전달되었는지, 혹은 Clock의 props로 전달되었는지, Clock component 를 사용하지 않고 직접 특정값을 넘겼는지 알 수 없다.

 

이러한 방식으로 데이터가 전달 방식은 "top-down"(위에서 아래로) 또는 "unidirectional"(단 방향인) 데이터 흐름이다. 어떤 state 든지 특정 component 가 소유하고 있을 것이고, 이러한 state로 부터 파생된 어떤 data나 UI 는 tree 구조 상에서 "below"(아래로) 만 영향을 미칠 수 있다.

 

모든 component 가 독립적인지 알아 보기 위해서 3개의 <Clock> component 를 그리는 <App> component 를 만들어 보자.

각 Clock 은 자신의 timer 를 가지고 있으며 독립적으로 갱신된다. React app 에서 component가 stateful 한지 stateless 한지는 시간이 지남에 따라 변경되는 component 의 세부구현 사항에 따라서 간주될 것이다. stateful 한 component 에서 stateless 한 component를 사용할 수도 있고 혹은 그 반대일 수도 있다.

댓글