본문 바로가기

[React] StateAirline Client (2) : Effect Hook

[React] StateAirline Client (2) : Effect Hook

 

React를 이용해 서버와 정보를 주고 받는 웹 페이지를 구성하기
-  Effect Hook을 활용하여 비동기 호출 및 Ajax 요청과 같은 side effect를 처리 
-  네트워크 요청이 느릴 경우에 표시되는 로딩 화면을 구현 

 

네트워크 요청을 통해 항공편 리스트를 받아오고, 도착지 정보 검색 기능 구현하기

 

  • 검색 조건을 변경하면 네트워크 요청을 보내고, 서버로부터 받은 응답을 결과 목록에 적용한다.

 

(1) Side Effect가 발생하는 함수를 Main 컴포넌트에서 호출

 

앞서 검색 조건을 담은 객체 condition이 바뀌면, Main 컴포넌트 자체 필터링에 의해서 검색 결과가 바뀌는 것을 알았다. (아래 참고)

// ** Main 컴포넌트에 구현되어 있었던 필터링 함수 : (1)편에서 사용했음 

const filterByCondition = (flight) => {
    let pass = true;
    if (condition.departure) {
      pass = pass && flight.departure === condition.departure;
    }
    if (condition.destination) {
      pass = pass && flight.destination === condition.destination;
    }
    return pass;
  };

 

  • 이번엔, 컴포넌트 내 필터 함수(filterByCondition)가 아닌, getFlight(condition) 함수를 이용해 본다. (아래 코드)
  •  getFlight함수는 setTimeout 을 이용해 의도적으로 delay를 주어서 응답이 느린 상황을 흉내냈다.
  • (3)번에서 진행할 StatesAirline API 호출은, 실제로 네트워크 상황에 따라 응답이 느릴 수 있다.
// ** FlightDataApi.js의 getFlight 함수

import flightList from '../resource/flightList';
import fetch from 'node-fetch';

if (typeof window !== 'undefined') {
  localStorage.setItem('flight', JSON.stringify(flightList));
}

export function getFlight(filterBy = {}) { //filterBy자리에 아무것도 들어오지 않았을때 초기값(기본값)을 줌

  let json = [];
  if (typeof window !== 'undefined') {
    json = localStorage.getItem('flight');
  }
  const flight = JSON.parse(json) || [];

  return new Promise((resolve) => {
    const filtered = flight.filter((flight) => {
      let condition = true;
      if (filterBy.departure) {
        condition = condition && flight.departure === filterBy.departure;
      }
      if (filterBy.destination) {
        condition = condition && flight.destination === filterBy.destination;
      }
      return condition;
    });

    setTimeout(() => {
      resolve(filtered);
    }, 500);
  });
}

 

  • 이 함수는, React 컴포넌트와는 별개로 작동하므로, Side Effect로 볼 수 있다.
  • Side Effect는 Effect Hook을 이용해 처리해야 한다.
  • condition 조건이 바뀌면, Effect Hook에 의해 getFlight(condition) 함수가 호출되도록 한다.
[getFlight 함수 설명]

인자로 객체를 넘겨주어야 한다.
예) { departure: 'ICN', destination: 'CJU' }
리턴값으로는 Promise 객체를 리턴하며, Promise 성공 시 항공편 목록이 함께 전달된다.

 

구현한 코드
  useEffect(() => {
    getFlight(condition)          // condition은 departure, destination가 key로 들어있는 객체 
      .then(resp => setFlightList(resp)) // 필터링된 결과값을 setFlightList 함수에 전달한다.
  }, [condition])                        // condition 상태가 변할 때마다 useEffect가 실행된다.

 


 

(2) 로딩 화면 제공

네트워크 요청이 느려질 때에 한동안 아무런 목록도 화면에 표시되지 않는다면, 사용자들은 뭔가 앱이 잘못 만들어져있다는 느낌을 받을 것이다. 그렇기 때문에 요청 이후에는 로딩 화면을 제공해야 사용자들은 안심하고 앱을 사용할 수 있다.

 

 

위의 그림과 같이 LoadingIndicator 라는 컴포넌트를 이용해야 한다.

 

LoadingIndicator 컴포넌트를 구현한 코드
function LoadingIndicator() {
  return (
    <img
      className="loading-indicator"
      alt="now loading..."
      src="loading.gif"
      style={{ margin: '1rem' }}
    />
  );
}

export default LoadingIndicator;

 

 

 앞선 포스팅에서 살펴 본 것처럼 요청이 진행 중일 때에는 isLoading 상태가 true, 그리고 요청이 완료되면 false 로 처리하도록 한다.

  1. 목록이 비워져 있음, 로딩 상태 on
  2. 네트워크 요청(fetch와 같은 AJAX 요청)에 따른 응답에 목록이 전송됨
  3. 목록을 업데이트함, 로딩 상태 off

 

구현한 코드 
import Head from 'next/head';
import { useEffect, useState } from 'react';
import { getFlight } from '../api/FlightDataApi';
import FlightList from './component/FlightList';
import LoadingIndicator from './component/LoadingIndicator';
import Search from './component/Search';
import json from '../resource/flightList';

export default function Main() {
  // 항공편 ** 검색조건 **을 담고 있는 "상태"
  const [condition, setCondition] = useState({                    
    departure: 'ICN',
    destination: ''
  });
  const [flightList, setFlightList] = useState(json);
  // 🟣 로딩을 구현하기 위한 state hook
  const [isLoading, setIsLoading] = useState(false);


  const search = ({ departure, destination }) => {
  // (1)편과 동일 : 주어진 검색 키워드에 따라 condition 상태를 변경시켜주는 함수
  };

  // 🟣 Effeck Hook을 이용한 AJAX 요청 
 
  useEffect(() => {
    setIsLoading(true)    // 데이터가 오기 전 로딩 화면을 구현하기 위함 
    getFlight(condition) 
      .then(resp => {
        setFlightList(resp)  // 결과값으로 flightList 상태를 바꿔준다.
        setIsLoading(false)}) // 데이터가 오고 난 후, 로딩 화면을 멈춤 
      }, [condition])     


  return (
    <div>
      <Head>
        <title>States Airline</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main>
        <h1>여행가고 싶을 땐, States Airline</h1>
        <Search onSearch = {search} />  
        <div className="table">
          <div className="row-header">
            <div className="col">출발</div>
            <div className="col">도착</div>
            <div className="col">출발 시각</div>
            <div className="col">도착 시각</div>
            <div className="col"></div>
          </div>
{/* 🟣 삼항연산자 사용, 네트워크 요청이 진행됨을 보여주는 로딩 컴포넌트(<LoadingIndicator/>)를 제공 */}          
          {isLoading ? <LoadingIndicator /> : <FlightList list={flightList} />}  
        </div>
        <div className="debug-area">
          <Debug condition={condition} />
        </div>
      </main>
    </div>
  );
}

 


 

* StatesAirline Server API 사용

 AJAX 호출에 따른 상태 변경 Diagram

 

 

 

StatesAirline Server API 

 

Request

 GET /flight
  • 파라미터가 없는 요청은 저장되어 있는 모든 항공편을 조회한다.
  • 해당 요청은 추가적인 파라미터(query parameter)를 사용할 수 있다.
  • 파라미터는 /flight?departure=ICN&destination=CJU 와 같이 사용할 수 있다.
parameter
형식 설명 필수 포함 여부
departure 출발지 (문자열) 특정 departure만 조회 필수 아님
destination 도착지 (문자열) 특정 destination만 조회 필수 아님

 

Response

다음 JSON 형식으로 응답이 온다.

[
   {
     "uuid": "af6fa55c-da65-47dd-af23-578fdba40bed",
     "departure": "ICN",
     "destination": "CJU",
     "departure_times": "2021-12-03T12:00:44",
     "arrival_times": "2021-12-03T12:00:44",
   },
   // 여러 개의 메시지
]
  • 메시지에서 사용하는 속성은 다음과 같다.
parameter
형식 설명
uuid 문자열 고유한 아이디
departure 문자열 출발지
destination 문자열 도착지
departure_times 문자열 출발 시간
arrival_times 문자열 도착 시간

 

 

REST API 호출
import flightList from '../resource/flightList';
import fetch from 'node-fetch';
import { useEffect } from 'react';

if (typeof window !== 'undefined') {
  localStorage.setItem('flight', JSON.stringify(flightList));
}

export function getFlight(filterBy = {}) {   // 검색을 위한 출발편, 도착편 객체 형태의 데이터
  // fetch를 이용한 REST API 호출 

  let queryString = '';
  if (filterBy.departure) {
    queryString = queryString + `departure=${filterBy.departure}&`;
  }
  if (filterBy.destination) {
    queryString = queryString + `destination=${filterBy.destination}`;
  }

  let endpoint = `http://ec2-13-124-90-231.ap-northeast-2.compute.amazonaws.com:81/flight?${queryString}`;

  return fetch(endpoint).then((resp) => resp.json());
  }
 
728x90
⬆︎