반응형

React에는 컴포넌트가 로드될 때, 업데이트 될 때 , 제거될 때 자동으로 호출되는 API 가 존재

=> React 의 Life Cycle API 

초기 생성 API

- 브라우저에 컴포넌트가 나타나기 전 호출되는 API

  • Constructor(props) : 컴포넌트 생성자 함수로 컴포넌트가 새로 만들어 질 때마다 호출
  • ComponentWillMount() : 컴포넌트가 화면에 나타나기 직전에 호출되는 API
    -> 주로 서버를 호출하는 용도로 사용되었는데 이 API를 더 필요하지 않다고 판단하여 사용하지 않게 됨 
    기존에 처리하던 부분은 constructor이나 componentDidMount에서 처리할 수 있음
  • componentDidMount() : 컴포넌트가 나타난 후 호출되는 api
    주로 , D3, masonry 처럼 DOM 을 사용하는 외부 라이브러리와 연동하거나, 데이터를 axios , fetch 등 통해 ajax 요청하거나, DOM 속성(스크롤 설정, 크기 등) 읽거나 직접 변경하는 작업 함

업데이트 API

- 로드된 컴포넌트가 props, state 변화로 인해 변경될 경우 호출되는 API 

  • componentWillReceiveProps(nextProps)  :
    자식 component가 부모 Component로 부터 새로운 props 를 받게 되었을 때 호출
    지금 사용되지 않음 -> getDerivedStateFromProps사용
  • static getDerivedStateFromProps(nextProps, prevState) :
    props로 받아온 값을 state로 동기화하는 작업을 해주어야할 경우 사용

    setState로 state 값을 설정하는 것이 아니라, 객체 형태로 state 값 리턴
    업데이트 할 값이 없다면 null 리턴
  • shouldComponentUpdate(nextProps, nextState)  :
    - 컴포넌트 최적화 작업에서 유용 
    - React는 변화가 발생한 DOM 만 찾아 변화시켜주는데 자식 Component가 직접적으로
    변하지 않았어도 부모가 Component가 리렌더링 되면 자식 컴포넌트도 다시 렌더링되며 Virtual Dom에 그려짐 
    - 기존 DOM 과 비교해서 변화가 발생했다면 실제 DOM 이 변경되고 그렇지 않다면 아무 작업도 하지 않음
    - 많은 렌더링 작업 시 부하가 생기고 기본적으로 true 값을 반환하고 우리가 조작하는 상황에 따라 false를 반환
        => render() 함수를 호출하지 않게 되고, Virtual DOM 에 리렌더링하는 작업을 막을 수 있음 
  • componentWillUpdate(nextProps, nextState) : 
    - shouldComponentUpdate에서 true가 반환되었을 때 호출
    - 주로 애니매이션 효과를 초기화하거나, 이벤트 리스너를 없애는 작업 

    - 이 함수 다음에 render 함수가 호출 (업데이트 이후 쓰이지 않음 아래 함수로 대체)
  • getSnapshotBeforeUpdate(prevProps, prevState)
    -  render 함수와 ComponentDidUpdate사이에 호출 
    - DOM 변화가 일어나기 직전의 DOM 상태를 가져오고, 여기서 그 상태 값(snapshot)을 반환해서 ComponentDidUpdate의
       3번째 매개변수로 받아 올 수 있다
    - 예 ) 현재 위치 스크롤바 유지하고 싶을 때 => 이 , api에서 현재 스크롤바 위치를 구해 그 값을 매개 변수로 반환 =>           componentDidUpdate에서 그 인자를 받아 스크롤바 위치 처리를 해주는 식 
    - render -> getShapshotBeforeUpdate -> 실제 DOM 업데이트 -> componentDidUpdate 순 진행
  • componentDidUpdate(prevProps, prevState, snapshot) : 
    - render 함수가 호출된 후 호출 
    - 이 시점에  this.props와 this.state는 변경된 상태 
    - 매개 변수로 받은 prevProps와 prevState는 말 그대로 변경 이전의 props와 state 값

제거 api

- 컴포넌트가 더 이상 사용되지 않을 경우 호출되는 api 

  • componentWillUnmount() : 
    - 주로 이벤트 제거 , setTimeout 의 clearTimeout 통한 제거 , setinterval의 clearInterval 통한 제거 작업
    - 외부 라이브러리 사용 시, 이 부분에서 해당 라이브러리의 dispose 기능 실행

src/StopWatch.js 수정 (기존 코드 + 주석 범위 안에 life cycle api 추가)

// StopWatch.js
import React, { Component } from "react";
 
class StopWatch extends Component {
  // class fields
  state = {
    sec: 0,
    buttonFlag: true,
    intervalFunction: null
  };
 
  /* Life Cycle API 영역 시작 */
  constructor(props) {
    super(props);
    console.log("constructor");
  }
 
  componentWillMount() {
    console.log("componentWillMount");
  }
 
  shouldComponentUpdate(nextProps, nextState) {
    console.log("shouldComponentUpdate");
    if (nextState.sec % 2 === 0) {
      console.log(`${nextState.sec} % 2 === 0`);
      return false;
    }
    return true;
  }
 
  componentWillUpdate(nextProps, nextState) {
    console.log("componentWillUpdate");
  }
 
  componentDidUpdate(prevProps, prevState) {
    console.log("componentDidUpdate");
  }
 
  /* Life Cycle API 영역 끝 */
 
  // stop watch 로직
  timer = () => {
    // 비구조화 할당 사용( { sec } 부분 )
    this.setState(({ sec }) => ({
      sec: sec + 1
    }));
  };
 
  // start 버튼 클릭
  start = () => {
    this.setState({
      sec: 0,
      buttonFlag: false,
      intervalFunction: setInterval(this.timer, 1000)
    });
  };
 
  // stop 버튼 클릭
  stop = () => {
    clearInterval(this.state.intervalFunction);
 
    this.setState({
      buttonFlag: true
    });
  };
 
  render() {
    return (
      <React.Fragment>
        <h1>Stop Watch</h1>
        <div>
          <b>{this.state.sec}</b>
          <span>초</span>
          {this.state.buttonFlag ? (
            <button onClick={this.start}>start</button>
          ) : (
            <button onClick={this.stop}>stop</button>
          )}
        </div>
      </React.Fragment>
    );
  }
}
 
export default StopWatch;

 

=> state 내의 sec가 0,2,4 즉, 짝수일 경우 컴포넌트를 변화시키지 않으므로 shouldComponentUpdate 까지만 호출
     실제 변화는 발생하지 않는다 / sec가 5 -> 6으로 변할 때 , stop 버튼을 눌러 state 안에 buttonFlag 값이 바뀌어
     마지막에 컴포넌트의 변화가 일어남

 

에러 처리 api

-render 함수 안에 에러가 발생하면, react 앱 기능이 정지  / 이 현상을 막기 위해 사용하는 api

 

  • componentDidCatch(error, info) :
    - render 함수 안에 에러가 발생 시 , 호출
    - state 에 error 이라는 변수를 생성 -> 해당 api 호출 시 setState로 error 값을 변경하여 render 함수 쪽에
       다시 에러용 UI를 보여줌
    - 주의점 : Component 자신의 render 함수에서 발생하는 에러는 잡을 수 없고, 자식 Component 내부의 render 함수에서 발생하는 에러를 잡음

src/StopWatch.js

-> ErrorMaker라는 인위적으로 에러를 생성하는 컴포넌트를 함수형 컴포넌트 형식으로 생성

    StopWatch 내부에서는 state의 인자로 error라는 값이 추가되었고 Life Cycle API 영역에서 마지막에 componentDidCatch가        추가.

     render 함수에서는 앱 중지를 방지하는 코드가 추가되었고, state의 sec가 3이 되었을 때, ErrorMaker를 생성.

// StopWatch.js
import React, { Component } from "react";
 
// Error 생성용 자식 Component
const ErrorMaker = () => {
  throw new Error("Error Occured!!");
  return <div />;
};
 
class StopWatch extends Component {
  // class fields
  state = {
    sec: 0,
    buttonFlag: true,
    intervalFunction: null,
    error: false
  };
 
  /* Life Cycle API 영역 시작 */
  constructor(props) {
    super(props);
    console.log("constructor");
  }
 
  componentWillMount() {
    console.log("componentWillMount");
  }
 
  shouldComponentUpdate(nextProps, nextState) {
    console.log("shouldComponentUpdate");
    if (nextState.sec % 2 === 0) {
      console.log(`${nextState.sec} % 2 === 0`);
      return false;
    }
    return true;
  }
 
  componentWillUpdate(nextProps, nextState) {
    console.log("componentWillUpdate");
  }
 
  componentDidUpdate(prevProps, prevState) {
    console.log("componentDidUpdate");
  }
 
  // render에서 에러 발생시 호출
  componentDidCatch(error, info) {
    console.log("componentDidCatch");
    this.setState({
      error: true
    });
  }
 
  /* Life Cycle API 영역 끝 */
 
  // stop watch 로직
  timer = () => {
    // 비구조화 할당 사용( { sec } 부분 )
    this.setState(({ sec }) => ({
      sec: sec + 1
    }));
  };
 
  // start 버튼 클릭
  start = () => {
    this.setState({
      sec: 0,
      buttonFlag: false,
      intervalFunction: setInterval(this.timer, 1000)
    });
  };
 
  // stop 버튼 클릭
  stop = () => {
    clearInterval(this.state.intervalFunction);
 
    this.setState({
      buttonFlag: true
    });
  };
 
  render() {
    // 다음과 같이 에러 발생시에 앱이 중지되는 것을 방지한다.
    if (this.state.error) return <h1>에러 발생!!</h1>;
 
    return (
      <React.Fragment>
        <h1>Stop Watch</h1>
        {this.state.sec === 3 && <ErrorMaker />}
        <div>
          <b>{this.state.sec}</b>
          <span>초</span>
          {this.state.buttonFlag ? (
            <button onClick={this.start}>start</button>
          ) : (
            <button onClick={this.stop}>stop</button>
          )}
        </div>
      </React.Fragment>
    );
  }
}
 
export default StopWatch;

결과는?

x 버튼 누르면 다음과 같이 에러 발생 시 정해둔 ul 가 나타남
-> 에러 발생하는 이유는 

    - 존재하지 않는 함수를 호출하려고 할 때(props로 받은 줄 알았던 함수 호출할 때)

    - 배열이나 객체를 받을 줄 알았을 때 존재하지 않을 때

 

render() {
    // 에러를 유발시키는 유형(실제로 props에서 onClick(), object, array를 받지 않았다고 가정)
    /*
        this.props.onClick();
        this.props.object.value;
        this.props.array.length;
    */
 
    // 다음과 같은 분기문을 추가하여 에러를 방지한다.
    if( !this.props.object || this.props.array || this.props.array.length === 0 ) return null;
 
    // ...
}

 혹시 누락 발생할 거 같으면 다음과 같이 defaultProps를 이용해 해당 값들을 초기화해서 사용

class Test extends Component {
    static defaultProps = {
        onClick: () => console.warning("onClick is not defined"),
        object: {},
        array: []
    }    
}

 

인용

https://dev-kani.tistory.com/11?category=849014
반응형

'Language Study > React' 카테고리의 다른 글

3. props와 state  (0) 2019.11.26
2. JSX  (0) 2019.11.26
1. 준비사항  (0) 2019.11.26

+ Recent posts