탕구리's 블로그

[React.js] #2 Props와 State와 LifeCycle 본문

javascript/React.js

[React.js] #2 Props와 State와 LifeCycle

탕구리당 2020. 12. 16. 12:08
반응형

1.React 컴포넌트

 

컴포넌트?

 

const ex = (<div>컴포넌트<div>);

 

컴포넌트는 반드시 하나의 Element만 리턴해야 합니다. (아래와 같은 코드 작성 불가)

 

const ex = (
  <div>1번<div>
  <div>2번<div>
);

 

  • 만약, 여러 개의 컴포넌트를 Return 하고 싶을 때에는 다른 태그로 한번 감싸주거나, 혹은 Fragment를 사용하도록 한다.
  • 각각의 컴포넌트는 독립적으로 작동.
const ex = (
    <Fragment>
      <div>1번<div>
      <div>2번<div>
    <Fragment>
 );

 

 

함수형 컴포넌트

  • props만 받아서 사용이 가능하다. (state사용 불가능)
  • life-cycle 함수가 존재하지 않습니다.
  • 클래스형 컴포넌트에 비교하여 속도가 빠르다.

** 현재는 react-hook을 통해 함수형 컴포넌트에서 "state"를 사용, 관리가 가능하며 life-cycle과 비슷한(?) 기능 또한 포함하고 있습니다.

// 함수형 컴포넌트 예시
const Header = props => {
  return (
    <header>
      {props.contents}
      <hr />
    </header>
  );
}

 

클래스형 컴포넌트

  • props, state 둘 다 사용 가능합니다.
  • life-cycle 함수가 존재합니다.
// 클래스형 컴포넌트 예시
class Body extends Component {
  render() {
    return (
    	<div> Hello World! </div>
    );
  }
}

 

2. props, state

props => 단방향(부모 -> 자식) 데이터 흐름, 수정 불가

  • props가 변경될 경우 자식 컴포넌트를 렌더링 합니다.
  • default prop 설정
  • prop-types 설정
import React, { Component } from 'react';

class App extends Component {
	constructor(props) {
    	super(props)
    }
	
	render() {
    	return (
        	<div> { this.props.contents } </div>
        )
    }
}

App.propTypes = {
  contents: PropTypes.string
};

App.defaultProps = {
  contents: '내용'
};

 

state => 데이터의 수정이 가능

  • state 변경 시 컴포넌트를 랜더링 합니다.
  • state 변경을 원할 경우 setState({}) 함수를 사용(단, setState({})는 비동기로 작동하기 때문에, 순서가 필요할 경우 Callback 스타일로 작성 하도록 해야한다.)
  • state는 직접 변경이 불가능하며 setState시 새로운 객체를 생성해주어야 합니다.
  • 객체의 불변성을 유지하기 위해 immutable.js를 함께 사용하기도 합니다.

import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import './App.css';

class App extends Component {
  constructor(props) {
  	super(props)
    this.state = {
    	date: new Date()
    }
  }
 
  componentDidMount() {
    this.timerID = setInterval(
      () => this.clock(),
      1000
    );
  }

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

  clock() {
    this.setState({
      date: new Date()
    });
  }
  
  render() {
    return (
      <div className="App">
      	{this.state.date.toLocaleTimeString()}
      </div>
    );
  }
}

 

위의 코드는 1초 단위로 clock() 함수를 실행하며 현재 시간을 가져오고 있습니다. 함수 호출시마다 setState()를 통해 "state.date"를 갱신해주고 있기 때문에 컴포넌트는 반복해서 랜더링 하는 과정이 일어납니다.

 

그리고 componentDidmount()와 compoentWillUnmount()라는 처음 보는 함수가 등장했는데 자세한 내용은 아래의 React-Life-Cycle에서 함께 알아보겠습니다.

 

* 객체가 아닌 함수 자체를 전달할 수 있습니다.

clock() {
    this.setState((prevState, props) => {
      console.log(prevState, props) 
      return {
        date: new Date()
      };
    }
  )}

setState는 첫 번째 인자로 previousState(현재 state 값)를 가져올 수 있고, 두 번째 인자로 props를 받게 됩니다.

파라미터 값을 통해 로직이 필요한 경우 사용할 수 있습니다.

 

3. Life-cycle

 

Mount? 처음으로 랜더링 될 때

  • life-cycle 순서
    • 생성
      • constructor()
        • 생성자 초기화
        • props를 state에 복사하면 안 됨.
      • static getDerivedStateFromProps()
        • props에 따른 state변화(setState설정은 사용하지 않음)
      • render()
      • componentDidMount()
        • Mount(render)된 이후 처음 1번만 실행
        • 초기화 이벤트 시 많이 사용(ajax요청, 리액트가 지원하지 않는 event 활용 시)
    • 업데이트
      • static getDerivedStateFromProps()
      • shouldComponentUpdate()
        • 최적화 작업 진행 시 사용(props || state 변경될 때 render를 할지 말지 결정)
      • render()
      • getSnapshotBeforeUpdate()
        • 랜더링 이후, DOM에 업데이트되기 직전에 실행
        • 이곳의 return값을 다음 componentDidUpdate에서 받아 올 수 있음
        • 무조건 반환 값이 있어야 함(null 도 괜찮음)
      • componentDidUpdate()
        • DOM이 완성된 이후 실행함 (초기 랜더링 시 실행 안 함)
        • setState 할 경우, 무한루프에 빠질 수 있으니 조심해서 사용해야 함
    • 마운트 해제
      • componentWillUnmount()
        • 만약 componendDidMount()에서 추가한 외부 라이브러리나, componendDidMount에서 등록한 리액트에서 지원하지 않는 event를 제거해주어야 한다.
    • 오류처리
      • static getDerivedStateFromError()
      • componentDidCatch()
    • 제거될 메서드(v17부터 완전히 제거된다고 함)
      • UNSAFE_componentWillMount()
      • UNSAFE_componentWillUpdate()
      • UNSAFE_componentWillReceiveProps()

 

import React, { Component, Fragment } from 'react';
import './App.css';

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      number: 0
    };
  }
  render() {
    return (
      <div className="App">
        <Child
          number={this.state.number}
          onClick={(e) => {
            this.setState(prevState => {
              return {
                number: prevState.number + 1
              }
            });
          }}
        />
      </div>
    );
  }
}


class Child extends Component {
  constructor(props) {
    super(props);
    this.state = {
      number: 0,
      a: 0 
    };
    console.log('Child constructor');
  }

  static getDerivedStateFromProps(nextProps, prevState) {
    // 특정 props 가 바뀔 때 state값을 초기화시킬때 사용
    console.log('Child static getDerivedStateFromProps');
    if (nextProps.number !== prevState.number) {
      return { number: nextProps.number };
    } else {
      return null;
    }
  }

  shouldComponentUpdate(nextProps, nextState) {
    /* 
      return 에따른 렌더링유무
      true: 렌더링
      false: 렌더링안함
    */
    console.log('Child shouldComponentUpdate');
    return true;
  }

  getSnapshotBeforeUpdate(prevProps, prevState) {
    console.log('Child getSnapshotBeforeUpdate');
    return 'snapshot!!';
    // return null;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    // getSnapshotBeforeUpdate의 return결과를 snapshot로 받아 올 수 있다.
    // setState사용할경우 무한루프에 들어갈 수 있으니 조심해서 사용해야함
    console.log(`Child componentDidUpdate ${snapshot}`);;
  }

  componentDidMount() {
    console.log('Child componentDidMount');
  }

  render() {
    console.log('Child render');
    return (
      <Fragment>
        <div>child</div>
        <div>{this.state.number}</div>
        <button onClick={this.props.onClick}>클릭</button>
      </Fragment>
    );
  }

}
export default App;
반응형
Comments