+ main goal

라이프사이클은 활용방법 – 언제, 어떻게 사용하느냐

라이프 사이클이 useEffect 훅으로의 변화 과정 이해

http client 호출 – REST API 이해 

fetch WEB API와 axios 의 장단점 이해

비동기 통신 – Promise 패턴과 async-await 패턴 이해

+ 자바스크립트는 싱글 쓰레드이다.

// 자바스크립트 실행순서 알기: 싱글쓰레드이다.
// 코드를 모두 실행하고 마지막에 예약된 큐를 확인해서 실행한다.
// 따라서 실행결과는 항상 동일하다.
 
console.log("A");
 
//예약
setTimeout(function () {
  console.log("B");
}, 0);
 
console.log("C");
 
//만일 루프가돌면?
// while(true) {}
 
//예약목록 확인 및 실행

+ 실습

player 에 사용된 mockup 데이터를 사용하지 않고 REST API를 호출하여 서버에서 players 데이터를 가져온다. player 추가 및 삭제에도 동일하게 서버 API를 호출한다.

라이프 사이클 메서드 활용

클래스 컴포넌트는 Component를 상속하였고 이 Component에는 여러가지 라이프 사이클 메서드가 있는데 그 중 가장 중요한 두가지를 살펴본다.

하나는 componentDidMount 라이프사이클 메서드이다. 이 메서드는 DOM(UI 화면)이 렌더링이 된 직후에 리액트에 의해서 자동으로 호출되는 메서드이다. 그러므로 REST API를 호출한다거나 3rd party 라이브러리를 로딩한다거나  하는 작업은 여기서 수행해야 한다. 왜냐하면 DOM이 렌더링 되기 전에 DOM에 접근하게 되면 에러가 발생하기 때문이다. 

두번째는 componentWillUnmount 라이프사이클 메서드이다. 이 메서드는 DOM이 파괴되기 직전에 리액트에 의해서 자동으로 호출되는 메서드이다. 그러므로 여기서는 리소스 해제 같은 작업을 여기서 수행해야 한다.

이 두가지 라이프 사이클 메서드는 훅이 되면서 useEffect 훅으로 변경된다는 점을 기억하자. useEffect는 부작용이 아니라 개발에서는 부가적으로 일어나는 효과라는 뜻이다. 즉, 화면이 렌더링 되기 전 혹은 된 후에 부가적으로 무언가를 처리하겠다는 의미가 된다.

REST api 호출 라이브러리

REST api를 호출하는 http 클라이언트 역할을 하는 대표적인 두가지가 있다. 첫번째는 fetch API이다. 이것은 표준 web API로서 추가적인 라이브러리 설치가 필요하지 않다. 하지만, 모든 브라우저가 아직 이 표준을 구현한것이 아니므로 브라우저 호환성을 고려한다면 아직 사용하기는 이르다고 볼 수 있다.

두번째는 axios라는 3rd party 라이브러리이다. 이 라이브러리는 모든 브라우저에서 동작하도록 구현되었다. axios는  interceptor 처리를 통해서 인증, 로그, 로딩바 띄우기 등 많은 작업을 자동화할 수 있어서 http client 라이브러리로 많이 사용되는 편이다.

두가지 모두 Promise를 리턴하므로 비동기 처리에 대한 이해와 Promise에 대한 이해, 그리고 async-await 같은 패턴을 숙지해야만 한다.

score 목록 가져오기 – 서버 연동

mockup 데이터인 players 데이터를 서버에서 가져와서 바인딩한다.

REST api는 아래와 같이  이미 만들어져 있다.

  • method: GET
  • URI: http://api.eastflag.co.kr:8000/api/score/list

먼저 axios 라이브러리를 설치한다.

yarn add axios

화면이 렌더링된 직후에 REST api를 호출해서 목록 데이터를 가져와야 하므로 componentDidMount 라이프 사이클 메서드에서 REST API를 GET 한다.

axios get 메서드는 Promise를 리턴한다. 그러므로 .then 메서드를 연결하여 리턴 결과를 받는다. then() 메서드에 첫번째 파라메터는 http responser가 반환된다. response 중에서 bodoy에 해당하는 부분은 data 이며 이것을 추출하여 state 를 업데이트한다.

App.jsx

  // 화면 렌더링 직후에 자동으로 호출되는 라이프사이클 메서드
  componentDidMount() {
    axios.get('http://api.eastflag.co.kr:8000/api/score/list')
      .then(response => {
        console.log(response);
        const {data} = response;
        this.setState({players: data});
      });
  }

score 추가하기 – 서버 연동

axios post 메서드를 호출한다. REST API 규격은 다음과 같다.

axios post를 호출한다음에 Promise가 리턴되면 then 메서드에서 state 상태를 업데이트한다.

  handleAddPlayer = (name) => {
    console.log(name);
    axios.post('http://api.eastflag.co.kr:8000/api/score', {name})
      .then(response => {
        console.log(response);
        const {data} = response;

        this.setState(prevState => {
          const players = [ ... prevState.players ];
          players.unshift(data);
          return { players };
        });
      });
  };

score 삭제하기 – 서버 연동

axios delete 메서드를 호출한다. REST API 규격은 다음과 같다.

axios delete를 호출한다음에 Promise가 리턴되면 then 메서드에서 state 상태를 업데이트한다.

  handleRemovePlayer = (id) => {
    axios.delete(`http://api.eastflag.co.kr:8000/api/score?id=${id}`)
      .then(response => {
        console.log(response);
        const {data} = response;
        if (data.result === 0) {
          this.setState(prevState => {
            return {
              players: prevState.players.filter(item => item.id !== id)
            }
          })
        }
      });
  }

전체 App.jsx는 다음과 같다.

import React from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import './App.css';
import {Header} from "./components/Header";
import {Player} from "./components/Player";
import {AddPlayerForm} from "./components/AddPlayerForm";
import axios from 'axios';

class App extends React.Component {
  state = {
    players: []
  };

  // 화면 렌더링 직후에 자동으로 호출되는 라이프사이클 메서드
  componentDidMount() {
    axios.get('http://api.eastflag.co.kr:8000/api/score/list')
      .then(response => {
        console.log(response);
        const {data} = response;
        this.setState({players: data});
      });
  }

  handleRemovePlayer = (id) => {
    axios.delete(`http://api.eastflag.co.kr:8000/api/score?id=${id}`)
      .then(response => {
        console.log(response);
        const {data} = response;
        if (data.result === 0) {
          this.setState(prevState => {
            return {
              players: prevState.players.filter(item => item.id !== id)
            }
          })
        }
      });
  }

  handleChangeScore = (id, delta) => {
    console.log('id: ' + id, 'delta: ' + delta);
    this.setState(prevState => {
      // 새로운 players 배열 생성
      const players = [ ...prevState.players ];
      players.forEach(player => {
        if (player.id === id) {
          player.score += delta;
        }
      })
      return { players }
    })
  }

  handleAddPlayer = (name) => {
    console.log(name);
    axios.post('http://api.eastflag.co.kr:8000/api/score', {name})
      .then(response => {
        console.log(response);
        const {data} = response;

        this.setState(prevState => {
          const players = [ ... prevState.players ];
          players.unshift(data);
          return { players };
        });
      });
  };

  render() {
    return (
      <div className="container p-3">
        <Header title="My scoreboard" players={this.state.players} />

        {/*Players List*/}
        { this.state.players.map(item =>
          <Player key={item.id}
                  name={item.name} score={item.score} id={item.id}
                  removePlayer={this.handleRemovePlayer}
                  changeScore={this.handleChangeScore} />) }

        <AddPlayerForm addPlayer={this.handleAddPlayer}></AddPlayerForm>
      </div>
    );
  }
}

export default App;