https://velog.io/@hidaehyunlee/React-State-%EB%9E%80
State
and Lifecycle
앞서, 리액트 렌더링 요소 : React Elements & React DOM 에서 UI를 업데이트 하는 한가지 방식을 배워보았고, 렌더링 된 출력값을 변경하기 위해 ReactDOM.render()
을 호출했다.
다만 컴포넌트의 pure
한 특성으로 내부에서 props를 변경하지 못하여 UI의 동적 변경에서 한계점을 맞이했다.
아래엔 기존의 렌더링 방식을 사용한 예제와 코드를 명시해놓았고, state의 핵심 특징과 state를 활용한 개선 목표 또한 명시하였다.
우리는 아래의 코드로 만들어진 상기 페이지의Ticking Clock
의 Rendered output을 바꾸기 위한 방법으로,
.
즉 UI를 업데이트 하기 위한 방법으로, 시간이 흐르는 단위인 매 초마다ReactDOM.render()
을 사용하여 Elements를 렌더링 해 주었다.
.
하지만 이러한 비효율적인 방식을 개선하고자 저번의 ticking 예제를 변형하며,
State
를 사용하여 UI를 업데이트 하는 방법을 배우고자 한다.
기존 Ticking 코드 : ReactDOM.render()을 매 초 호출
function tick() { const element = ( <div> <h1>Hello, horiz.d</h1> <h2>It is {new Date().toLocaleTimeString()}.</h2> </div> ); ReactDOM.render( element, document.getElementById('root')); } . setInterval(tick, 1000);
직전의 예제는 ReactDOM.render()를 매 초 호출하며 변화사항을 DOM이 매 초 새로 렌더링 하여 UI를 업데이트 했다.
일단 하나의 함수 tick()
을 아래와 같이 Clock컴포넌트
와 tick()함수
로 분할 해준다.
function Clock(props) { // 컴포넌트 return ( <div> <h1>Hello, horiz.d</h1> <h2>It is {props.date.toLocaleTimeString()}.</h2> </div> ); } . function tick() { //함수 ReactDOM.render( <Clock date={new Date()} />, // Clock 컴포넌트 사용 document.getElementById('root') ); } // setInterval이 callback으로 tick함수를 사용했고 //tick내에 삽입된 <Clock /> 컴포넌트의 렌더링이 수행됨 setInterval(tick, 1000);
State를 사용한 개선 목표
위의 코드는 여전히 가장 중요한 요구사항이 결여돼있다.
우리는 컴포넌트Clock
이 타이머를 설정하여 매 초 UI를 업데이트하도록 만들고자 한다.
.
다시 말해, 가장 이상적으로 한 번만 코드를 작성하고Clock
이 스스로 업데이트 하도록 만들 것 이다.ReactDOM.render( <Clock />, document.getElementById('root') );
이를 위해,
Clock
컴포넌트에state
를 추가 할 것이다.
.
State는 Props와 유사하지만, 비공개이며 컴포넌트에 의해 제어가 가능하다.
직전의 Component & Props 에서 알아봤 듯, Component는 pure
하므로 전달받은 props
를 존중하여 props의 값을 자신의 내부에서 수정하지 않는다.
이러한 특질로 인해 컴포넌트의 단독 사용은 리액트의 Dyanamic한 앱UI 설계에 제약을 줄 수 있는데,
위에서 언급한 컴포넌트에 의한 제어 가능한 state의 특징을 사용하여 우리는 UI의 Dynamic한 수정을 가능하게 만들 수 있다.
아래의 다섯 스텝을 거쳐 Clock : 함수형 컴포넌트
를 클래스형 컴포넌트
로 변환할 수 있다.
5 STEPs
- 기존의
함수형 컴포넌트
와 동일한 이름의,
ES6 class 컴포넌트 ( extends React.Component )
를 생성한다.
- 생성한
클래스형 컴포넌트
에render()
라고 불리는 단일의 빈 method를 추가한다.
- 기존의
함수형 컴포넌트
의 body를Class 컴포넌트
내부의 render()의{ }
내에 옮겨준다.
render() {body}
에 있는props
를this.props
로 교체해준다.
- 기존의
함수형 컴포넌트
선언을 제거해준다.
변환 완료된 Clock : Class형 컴포넌트
class Clock extends React.Component { render() { return ( <div> <h1>Hello, horiz.d!</h1> <h2>It is {this.props.date.toLocaleTimeString()}.</h2> </div> ); } }
이제 Clock
컴포넌트는 함수가 아니라 클래스형 컴포넌트로 정의되었고, 변환된 Clock Component 내에 명시된 render()
메소드는 Update가 발생할 때 마다 호출 될 것이다.
하지만 우리가 <Clock />
컴포넌트를 동일한 DOM node
에서 렌더링 하는 한, Clock Class
의 단 하나의 instance
만이 사용 될 것이다.
따라서 local state
, lifecycle methods
와 같은 추가적인 features를 사용하여 이를 보완해야 한다.
Local State
를 클래스 컴포넌트에 추가해당 process에서는 아래의 3STEP을 따라,
date
를 props 에서 state 로 옮겨줄 것 이다.
STEPs
클래스 내 render() 메소드 안에 있는 this.props.property
를 this.state.date
로 교체
class컴포넌트 내에 초기의 this.state
를 할당하는 class constructor
를 추가
기존 ReactDOM.render()내의 렌더링 대상 요소인 <Clock />
element에서 date
prop을 제거
STEP1 : props를 state로 교체
class Clock extends React.component { render() { return ( <div> <h1>Hello, horiz.d!</h1> <h2>It is {this.state.date.toLocalTimeString()}.</h2> </div> ); } }
STEP2 : class constructor 추가 ( 초기 this.state를 할당하는 )
class Clock extends React.component { constructor(props) { super(props); this.state = {date. new Date()}; } . render() { return ( <div> <h1>Hello, horiz.d</h1> <h2>It is {this.state.date.toLocalTimeString()}.</h2> </div> ); } }
위의 코드에서, 클래스 컴포넌트가 받은
props
를 어떻게base constructor
로 전달했는지 주목하자.
constructor(props){ super(props); this.state = {date: new Date()}; }
- rule : 클래스 컴포넌트는 항상
props
를 가지고base constructor
를 호출해야만 한다.
STEP3 : 렌더링 부
<Clock /> element
에서date
프로퍼티를 제거ReactDOM.render( <Clock />, document.getElementById('root') );
이제 아래의 Process 3에서 Clock 컴포넌트
가 자신의 timer를 설정하고, 매 초마다 자신을 업데이트하도록 만들 것이다.
Lifcecycle Methods
를 클래스 컴포넌트에 추가많은 컴포넌트를 가진 애플리케이션 안에선, 컴포넌트가 파괴될 때 이들이 차지하는 resource 자원을 해제해 주는 것이 매우 중요하다.
이를 아래의 Mount/Unmount
방식으로 진행할 것 이다.
Mounting : Clock
이 Element에서 DOM
으로 처음 렌더링 될 때, timer
를 설정해주고자 한다.
Unmounting : Clock
에 의해 만들어진 DOM
이 제거될 때는, 그 timer
를 지워주고자 한다.
Component가 mount 혹은 unmount될 때 특정 코드를 실행되게 만들어주기 위해, Component class
에 special methods
: componentDidMount() & componentWillUnmount()
를 선언해 줄 수 있다.
이들을 이르러 Lifecylce methods라고 한다.
Lifecycle Methods 추가
class Clock extends React.Component { constructor(props) { super(props); this.state = {date: new Date()}; } . componentDidMount() { 해당 부분에 Mount시의 실행 코드를 입력 가능 } componentWillUnmount() { 해당 부분에 Unmount시의 실행 코드를 입력 가능 } . render() { <div> <h1>Hello, horiz.d!<h1> <h2>It is {this.state.date.toLocaleTimeString()}.</h2> </div> }
- 위에서 선언된
componentDidMount & componentWillUnmount
를 Lifecycle Method라고 한다.
.- 위의 생명주기 메소드의 내부에 Mount & Unmount 시 실행시킬 코드를 작성해줄 수 있다.
[1] componentDidMount( )
componentDidMount()
메소드는 소속Component
의 산출물이DOM
에 렌더링 된 직후에 실행된다.
그리고 이것은 우리가 계획한timer
를 넣어주기 적절한 곳이다.componentDidMount(){ this.timerID = setInterval( () => this.tick(), 1000 ); }
timer ID를 어떻게 this에 저장했는지 주목하자(
this.timerID
)
this.props
가 React에 의해 설정되고 this.state
가 특별한 의미를 가지는데 반해,
componentDidMount()
내에는 timerID
처럼, 필요에 따라 직접 추가적인 fields를 만들어 Data Flow에 속하지 않는 무언가를 보관할 수 있다.
그리고 resource 최적화를 위해 아래의 componentWillUnmount( )에서 이들을 제거 해준다.
[2] componentWillUnmount( )
componentWillUnmount() { clearInterval(this.timerID); } // this.timerID를 제거해준다.
마침내, Clock 컴포넌트
가 매 초마다 동작시킬 tick()
이라고 불리는 메소드를 클래스 내에 암시할 것이다.
최종 코드
class Clock extends React.component{ constructor(props){ super(props); this.state = {date: new Date()}; } // mount LifeCycle메소드 : render 직후 실행됨 componentDidMount() { this.timerID = setInterval( () => this.tick(), // 해당 컴포넌트 내의 tick() 메소드를 실행한다. 1000 ); } // unmount LifeCycle메소드 componentWillUnmount(){ clearInterval(this.timerID); } // 컴포넌트가 매 초 실행하게 되는 tick()메소드 tick() { this.setState({ date: new Date() }); } . render() { return ( <div> <h1>Hello, horiz.d!</h1> <h2>It is {this.state.date.toLocalTimeString()}.</h2> </div> ); } } . ReactDOM.render( <Clock />, document.getElementById('root') );
Recap
<Clock />
가ReactDOM.render()
로 전달되었을 때 React는Clock 컴포넌트의 constructor
를 호출합니다. Clock이 현재 시각을 표시해야 하기 때문에현재 시각이 포함된 객체
로this.state
를 초기화합니다. 나중에 이 state를 업데이트할 것입니다.
.- React는 Clock 컴포넌트의
render() 메서드
를 호출합니다. 이를 통해 React는 화면에 표시되어야 할 내용을 알게 됩니다. 그 다음 React는 Clock의 렌더링 출력값을 일치시키기 위해 DOM을 업데이트합니다.
.- Clock 출력값이 DOM에 삽입되면, React는
componentDidMount() 생명주기 메서드
를 호출합니다. 그 안에서 Clock 컴포넌트는 매초컴포넌트의 tick() 메서드
를 호출하기 위한타이머
를 설정하도록 브라우저에 요청합니다.
.
- 매초 브라우저가
tick() 메서드
를 호출합니다. 그 안에서 Clock 컴포넌트는setState()
에 현재 시각을 포함하는 객체를 호출하면서 UI 업데이트를 진행합니다.
.- setState() 호출 덕분에 React는 state가 변경된 것을 인지하고 화면에 표시될 내용을 알아내기 위해 render() 메서드를 다시 호출합니다.
.- 이 때 render() 메서드 안의 this.state.date가 달라지고 렌더링 출력값은 업데이트된 시각을 포함합니다. React는 이에 따라 DOM을 업데이트합니다.
.
- Clock 컴포넌트가 DOM으로부터 한 번이라도 삭제된 적이 있다면 React는 타이머를 멈추기 위해 componentWillUnmount() 생명주기 메서드를 호출합니다.
State
의 올바른 사용법State의 직접 수정은 금물
state의 값을 직접 수정해선 안된다, 이경우 리액트는 수정된 state에 따라 다시 렌더링하지 않는다. 대신 setState()를 사용하여 리액트가 이를 인지하고 수정에 따라 새로 렌더링할 수 있도록 해주자.
.
this.state.comment = 'Hello';// Correct this.setState({comment: 'Hello'});
State Update 들은 Asynchronous(비동기)일 수 있다.
React는 성능을 위해 여러
setState()
호출을 단일 업데이트로 한번에 처리할 수 있다.
.
this.props
와this.state
는 비동기적(Asynchronous)으로 업데이트 될 수 있기 때문에, 다음 state의 계산이 이들에게 의존적이면 안된다.Wrong Case - Will be fail
// Wrong this.setState({ counter: this.state.counter + this.props.increment, });
Correct Case1
this.setState((state, props) => ({ counter: state.counter + props.increment }));
Correct Case2
this.setState(function(state, props) { return { counter: state.counter + props.increment }; });
State Update 들은 병합 된다.
데이터는 아래로 흐른다