-
리액트 주요개념 - State and LifecycleIT/리액트 2022. 1. 15. 18:50728x90
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 클래스로 변환할 것입니다.
- React.Component를 상속받는 동일한 이름의 클래스를 생성
- render() 메소드 추가
- Clock 내용을 render() 메서드 안으로 옮김
- render() 내용 안에 있는 props를 this.props로 변경
- 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로 이동하는 과정입니다.
- render() 메서드 안에 있는 this.props.date를 this.state.date로 변경합니다.
//<h2>It is {this.props.date.toLocaleTimeString()}.</h2> <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
- 초기 this.state를 지정하는 class constructor를 추가합니다.
constructor(props){ super(props); this.state = {date: new Date()}; }
- <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') );
호출순서
- <Clock />가 ReactDom.render()로 전달 -> React가 Clock 컴포넌트의 constructor 호출 -> this.state 초기화
- React가 clock 컴포넌트의 render() 호출 -> DOM 업데이트
- clock 출력밧이 DOM에 삽입 -> componentDidMount() 호출 -> 매초 tick 메서드 호출하기 위한 타이머 설정을 브라우저에 요청
- 매초 브라우저가 tick 메서드 호출 -> setStae 호출 -> render 다시 호출 -> DOM 업데이트
- Clock 컴포넌트가 DOM으로부터 한 번이라도 삭제된 적 있다면 React는 타이머를 멈추기 위해 componentWillUnmount 호출
State 올바르게 사용하기
setState()에 대해 주의해야할 사항:
- 직접 State를 수정하지 않는다.
위의 코드는 컴포넌트를 다시 렌더링하지 않기때문에 setState를 사용해야 합니다.this.state.comment = 'Hello';
this.state를 지정할 수 있는 유일한 공간은 바로 constructor입니다!!this.setState({commnet: 'Hello'});
- State 업데이트는 비동기적일 수도 있다.React는 성능을 위해 여러 setState() 호출을 단일 업데이트로 한꺼번에 처리할 수 있습니다. this.props와 this.state가 비동기적으로 업데이트될 수 있기 때문에 다음 state를 계산할 때 해당 값에 의존해서는 안됩니다.
위의 코드는 다음과 같이 고칠 수 있습니다.this.setState({ counter: this.state.count + this.props.increment, }); //카운터 업데이트 실패할 수 있음
위의 표현방식은 화살표 함수로 기존의 함수 표현식에서 function 키워드를 삭제하고 인자를 받는 매개변수의 괄호와 코드블록 사이에 화살표(=>)를 넣어 표현한 것입니다.this.setState((state, props) => ({ counter: state.counter + props.increment }));
- State 업데이트는 병합된다.
setState를 호출할 때 React는 제공한 객체를 현재 state로 병합합니다. 위의 코드와 state는 같이 다양한 독립적인 변수를 포함할 수 있는데 각각의 setState()로 변수를 독립적으로 업데이트할 수 있습니다.constructor(props) { super(props); this.state = { post: [], comments: [] }; }
데이터는 아래로 흐릅니다.
모든 컴포넌트는 특정 컴포넌트의 존재 유무를 알 수 없고 특정 컴포넌트가 함수나 클래스로 정의되어있는지에 대해서도 관심을 가질 필요가 없습니다.
이러한 특징 때문에 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'IT > 리액트' 카테고리의 다른 글
리액트 주요개념 - 조건부 렌더링 (0) 2022.01.16 리액트 주요개념 - 이벤트 처리하기 (0) 2022.01.15 리액트 주요개념 - Component와 Props (0) 2022.01.15 리액트 주요개념 - 엘리먼트 렌더링 (0) 2022.01.15 리액트 주요개념 - JSX (0) 2022.01.12