본문 바로가기

[React] StateAirline Client (1) : 상태 끌어올리기

[React] StateAirline Client (1) : 상태 끌어올리기

 

*  상태 끌어올리기, 데이터 흐름 개념을 활용하여 항공편 검색 기능을 구현한다.
-  React의 데이터 흐름에 대해 이해하고 state를 전달할 수 있다.
-  상태 끌어올리기를 활용하여, 원하는 컴포넌트에서 state를 변경할 수 있다.
-  검색 조건을 변경하면, 해당 검색 조건에 맞는 항공권이 필터링되어 목록에 표시되게 한다.

 


 

디렉토리 구조

.
├── README.md
├── __tests__
│   └── index.test.js # 테스트 파일
├── api
│   └── FlightDataApi.js # 항공편 정보를 받아오는 API
├── package.json
├── pages
│   ├── Main.js # 첫 화면 컴포넌트, 필터링 상태를 담고 있다.
│   ├── component
│         ├── Debug.js            # 디버그용 컴포넌트 (테스트 통과에 필요)
│         ├── Flight.js           # 단일 항공편
│         ├── FlightList.js       # 항공편 목록
│         ├── LoadingIndicator.js # 로딩 컴포넌트
│         └── Search.js           # 검색 도구 컴포넌트, 필터링 상태를 변경한다.
├── public
├── resource
│   └── flightList.js # 하드코딩된 항공편 정보
└── styles
    └── globals.css # 스타일 시트

 

상태 끌어올리기

 

상태 관리, 상태 끌어올리기

Search 컴포넌트는 필터링 조건을 업데이트하는 액션을 보낸다.

  • React의 데이터 흐름 방식에 따라 "필터링 조건(condition)"이라는 상태는, Main 컴포넌트에 위치하며, 이에 따라 FlightList에 영향을 준다.
  • 이 흐름의 전반이 바로 상태 끌어올리기이다.
    • Search에 존재하는 버튼 클릭이 Main의 상태 변화를 만든다.
    • Main의 상태 변화는 FlightList의 목록을 업데이트한다.
// ** Main.js

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 Debug from './component/Debug';
// 후반 테스트를 진행할 때 아래 import를 삭제합니다.
import json from '../resource/flightList';

export default function Main() {
  // 항공편 검색 조건을 담고 있는 상태
  const [condition, setCondition] = useState({                    
    departure: 'ICN',
  });
  const [flightList, setFlightList] = useState(json);

  // 주어진 검색 키워드에 따라 condition 상태를 변경시켜주는 함수
  const search = ({ departure, destination }) => {
    if (
      condition.departure !== departure ||
      condition.destination !== destination
    ) {
      // console.log('condition 상태를 변경시킨다');
      setCondition({
        departure: departure,             // 🟣 (설명1)
        destination: destination
      })                 

      // search 함수가 전달 받아온 '항공편 검색 조건' 인자를 condition 상태에 적절하게 담는다.
    }
  };

  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;
  };

// search 함수를 onSearch라는 이름으로 Search 컴포넌트로 내려준다.
  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>
          <FlightList list={flightList.filter(filterByCondition)} />
        </div>

        <div className="debug-area">
          <Debug condition={condition} />
        </div>
      </main>
    </div>
  );
}

 

🟣  (설명1) + 리액트를 쓰며 익숙해져야 할 것 (구조분해할당)

  const search = ({ departure, destination }) => {
    if (
      condition.departure !== departure ||              
      condition.destination !== destination
    ) {
      setCondition({
        departure,        // 🟣 departure: departure 과 똑같다.
        destination
      })                 
    }
  };

 

다른 예시
// 같은 표현 방식이다. 익숙해지자!

// 1)
const {
      target: { value },
    } = event;

// 2) 
const value = event.target.value
 
 

 
 
// ** Search.js

import { useState } from 'react';

function Search({onSearch}) {
  const [textDestination, setTextDestination] = useState('');

  const handleChange = (e) => {
    setTextDestination(e.target.value.toUpperCase());
  };

  const handleKeyDown = (e) => {
    // 🟣 (설명1) 검색 버튼을 누르거나, 엔터를 치면 search 함수가 실행
    if (e.type === 'keydown' && e.code === 'Enter') {
      handleSearchClick();
    }
  };

  const handleSearchClick = () => {
    // console.log('검색 버튼을 누르거나, 엔터를 치면 search 함수가 실행');
    // 상위 컴포넌트에서 props를 받아서 실행 
    onSearch({departure: 'ICN', destination: textDestination}) // **
  };

  return (
    <fieldset>
      <legend>공항 코드를 입력하고, 검색하세요! </legend>
      <span>출발지</span>
      <input id='input-departure' type='text' value='ICN'></input>
      <span>도착지</span>
      <input
        id='input-destination'
        type='text'
        value={textDestination}
        onChange={handleChange}
        placeholder='CJU, BKK, PUS 중 하나를 입력하세요'
        onKeyDown={handleKeyDown}
      />
      <button id='search-btn' onClick={handleSearchClick}>
        검색
      </button>
    </fieldset>
  );
}

export default Search;

 

🟣 (설명1)

[e.type과 e.code]

e.type
은 이벤트의 타입을 나타내는 문자열 속성이다. 이벤트 객체 e에는 이벤트에 대한 정보가 담겨있는데, 이 정보 중 type 속성은 이벤트의 타입을 나타내며, code 속성은 눌린 키의 코드를 나타낸다.

-  위 코드에서 handleKeyDown 함수는 keydown 이벤트가 발생했을 때 실행되므로, e.type은 항상 'keydown'이다.
(keydown 이벤트는 키보드에서 어떤 키가 눌렸을 때 발생)
-  만약 e.type === 'keydown'만 있으면 키보드가 눌릴 때마다 검색 결과가 나오며 마지막 키가 눌리기 전의 결과만 확인할 수 있다. ('ICN'을 눌렀다면 'IC'까지의 결과만, 그 뒤에 다른 키보드를 한 번 더 눌러야만 'ICN'의 결과를 볼 수 있음)
-  따라서 위 코드에서 if 문은 엔터 키('Enter')가 눌렸을 때 handleSearchClick() 함수를 실행하는 역할을 한다.


[참고: key code를 확인해주는 사이트]

https://www.toptal.com/developers/keycode

예: enter에 대한 key code

 


 

만약 departure의 값도 필터링하고 싶다면 

 

import { useState } from 'react';

function Search({ onSearch }) {            
// 🟣 textCondition에 departure, destination 세팅
  const [textCondition, setTextCondition] = useState({
                                                       departure: 'ICN', 
                                                       destination: ''   });

  const handleKeyDown = (e) => {
    if (e.type === 'keydown' && e.code === 'Enter') {
      handleSearchClick();
    }
  };

  const handleSearchClick = () => {
    onSearch(textCondition);
  };

  return (
    <fieldset>
      <legend>공항 코드를 입력하고, 검색하세요</legend>
      
      <span>출발지</span>
 <!--  🟣 기존 textConditio을 풀여 쓰고, departure 값을 덮어 쓴다. -->    
      <input
        id='input-departure'
        type='text'
        value={textCondition.departure}
        onChange={(e) =>
          setTextCondition({ ...textCondition, departure: e.target.value.toUpperCase() })
        } 
      ></input>    

      
      <span>도착지</span>
 <!--  🟣 기존 textConditio을 풀여 쓰고, destination 값을 덮어 쓴다. -->          
      <input
        id='input-destination'
        type='text'
        value={textCondition.destination}
        onChange={(e) =>
          setTextCondition({ ...textCondition, destination: e.target.value.toUpperCase() })
        }                                  
        placeholder='CJU, BKK, PUS 중 하나를 입력하세요'
        onKeyDown={handleKeyDown}
      />
      <button id='search-btn' onClick={handleSearchClick}>
        검색
      </button>
    </fieldset>
  );
}

export default Search;

 

728x90
⬆︎