State와 라이프 사이클
지금까지 우리는 랜더링을 업데이트하는 한가지 방법만을 배웠다.
바로 ReactDom.render()를 호출하여 랜더링된 출력을 변경하는 것이다.
이번 포스팅에서는 앞서 만든 시계 예제를 재사용가능하고 캡슐화 시키는 방법에 대해서 알아볼 것이다.
변경 후에는 저절로 시간이 변경될때마다 자동으로 랜더링 될 것이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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); | cs |
먼저 위의 코드와 같이 Clock 컴포넌트를 만드는 것으로 캡슐화를 시작할 수 있다.
또한, tick함수가 아닌 Clock에서 시간을 업데이트해줘야한다.
이를 구현하려면 시간에 대한 state를 Clock 컴포넌트에 추가해줘야한다.
state는 props와 비슷하지만 컴포넌트에서 컨트롤할 수 있다는 것이 차이점이다.
함수를 클래스로 변환
1 2 3 4 5 6 7 8 9 10 | class Clock extends React.Component { render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.props.date.toLocaleTimeString()}.</h2> </div> ); } } | cs |
Clock과 같은 functional component를 5단계에 걸쳐서 클래스로 변환할 수 있다.
1. React.Component를 상속받는(extends) 동일한 이름의 ES6 클래스를 만든다
2. render라는 빈 메소드를 추가한다.
3. 함수 본문을 render 메소드로 옮긴다.
4. props를 render 메소드 본문의 this.props로 변경한다.
5. 나머지 빈 함수 선언을 삭제한다.
이 과정을 마치면 Clock은 이제 함수가 아닌 클래스로 선언된다.
클래스에 로컬 state 추가
클래스가 완성되었으니 우리는 이제 3단계로 props를 state로 옮길것이다.
1 2 3 4 5 6 7 8 9 10 | class Clock extends React.Component { render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.state.date.toLocaleTimeString()}.</h2> </div> ); } } | cs |
1) render() 메소드에서 this.props.date를 this.state.date로 바꾼다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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> ); } } | cs |
2) this.state를 초기화 하는 constructor를 추가한다.
여기서 constructor에 props가 인자로 들어간것을 주목하자.
그리고 반드시 constructor안에서 super()를 호출해야한다.
1 2 3 4 | ReactDOM.render( <Clock />, document.getElementById('root') ); | cs |
3) Clock 컴포넌트를 호출하는 태그에서 date를 제거한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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') ); | cs |
결과적으로 다음과 같은 코드로 변경 된다.
클래스에 라이프 사이클 메소드 더하기
많은 컴포넌트들로 구성된 어플리케이션에서는
컴포넌트가 사라질 때 사용된 리소스를 확보하는 것은 매우 중요하다.
어떤 컴포넌트가 DOM에 처음 랜더링 될 때 이것을 리액트에서는 "마운트"라고 한다.
또한 컴포넌트가 DOM이 제거될 때 "마운트 해제" 또는 "언마운트"라고 한다.
컴포넌트가 마운트 및 마운트 해제될 때 특정 코드를 실행하기 위해서
컴포넌트 클래스에 특수 메소드를 선언할 수 있다.
이러한 특수 메소드를 "라이프 사이클 후크(lifecycle hooks)"라고 한다.
1 2 3 4 5 6 | componentDidMount() { this.timerID = setInterval( () => this.tick(), 1000 ); } | cs |
componentDidMount() 메소드는 컴포넌트가 DOM에 랜더링 된 후에 실행이 된다.
Clock에서 타이머를 설정하기에 좋은 장소이다.
1 2 3 | componentWillUnmount() { clearInterval(this.timerID); } | cs |
그리고 componentWillUnmount() 라이프 사이클 후크에서 타이머를 종료한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | 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> );2) } } ReactDOM.render( <Clock />, document.getElementById('root') ); | cs |
마지막으로 매초 마다 실행되는 함수를 tick에 선언한다.
여기서 state를 갱신하기 위해 this.setState 메소드를 사용한다.
절대 직접 접근으로 state를 변경해서는 안된다. 이는 불변성(immutable)에 대한 위반이다.
변경이 완료되면 매 초 마다 시계가 돌아간다.
무슨일이 일어나고 있는지 간단하게 요약하면 다음과 같다.
1) <Clock />이 ReactDOM.render에 전달되면 React가 Clock 컴포넌트의 constructor를 호출한다.
constructor에서는 this.state를 현재 시간을 포함하는 객체로 초기화한다.
2) 그 후 Clock 컴포넌트 내의 render 메소드를 호출한다.
그런 다음 리액트는 Clock의 render 출력과 동일하도록 DOM을 업데이트한다.
3) Clock 출력이 DOM에 삽입되면 리액트는 componentDidMount() 메소드를 호출한다.
그 안에 Clock 컴포넌트가 브라우저에 tick()을 한번씩 호출하는 타이머를 설정하도록 요청한다.
4) 매 초마다 브라우저는 tick() 메소드를 호출한다.
그 안에 Clock 컴포넌트는 현재 시간을 포함하는 객체로 setState()를 호출하여 UI 업데이트를 예약한다.
setState() 메소드 덕분에 리액트는 상태가 변경된것을 감지할 수 있고 render() 메소드를 호출하여 DOM을 업데이한다.
5) Clock 컴포넌트가 DOM에서 제거 되면 React가 componentWillUnmount() 메소드를 호출하여 타이머가 중지된다.
State 올바르게 사용하기
setState에 대해 알아야할 3가지가 있다.
1) state를 직접 변경하지 않을 것
state를 직접 변경하면 리액트가 이를 감지하고 다시 랜더링 하지 않으므로 setState를 이용한다.
state를 유일하게 할당 할수 있는 곳은 constructor다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // Wrong this.setState({ counter: this.state.counter + this.props.increment, }); // Correct this.setState((prevState, props) => ({ counter: prevState.counter + props.increment })); // Correct this.setState(function(prevState, props) { return { counter: prevState.counter + props.increment }; }); | cs |
2) state 업데이트는 비동기일 수도 있다.
리액트는 여러 setState()의 호출 성능 향상을 위해 단일 업데이트로 배치할 수 있다.
따라서 this.props 및 this.state가 비동기적으로 갱신될 수 있으므로 state를 계산할때 값을 신뢰하면 안된다.
따라서 위의 첫번째 코드는 업데이트 되어지지 못할수도 있다.
이 문제를 해결하기 위해서는 객체가 아닌 함수를 받아들이는 두번째 방법을 사용하는것이 좋다.
이 함수는 이전상태를 첫번째 인자로 받고 업데이트된 props를 두번째 인자로 받는다.
두번째 방법은 arrow function을 사용했지만, 세번째처럼 일반 함수에서도 동일하게 동작한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | constructor(props) { super(props); this.state = { posts: [], comments: [] }; } componentDidMount() { fetchPosts().then(response => { this.setState({ posts: response.posts }); }); fetchComments().then(response => { this.setState({ comments: response.comments }); }); } | cs |
3) state 업데이트가 병합된다.
setState() 메소드를 호출하면 리액트가 제공한 객체를 현재 상태로 병합한다.
예를들어 사용자의 state에는 독립된 변수가 포함될수 있다.
이 때, state 내부의 독립된 변수를 각각 업데이트 한 후 state에 병합 할 수 있다는 것이다.
'개발' 카테고리의 다른 글
[ReactJS] 조건부 랜더링(Conditional Rendering) (0) | 2017.06.13 |
---|---|
[ReactJS] 이벤트 핸들링(Event Handling) (0) | 2017.06.13 |
[ReactJS] 컴포넌트와 Props (0) | 2017.06.12 |
[ReactJS] 랜더링(Rendering) (0) | 2017.06.12 |
[ReactJS] JSX (0) | 2017.06.12 |
댓글