* 상태 끌어올리기, 데이터 흐름 개념을 활용하여 항공편 검색 기능을 구현한다.
- 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
만약 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
'FE > React' 카테고리의 다른 글
DOM reference를 잘 활용할 수 있는 useRef (0) | 2023.04.18 |
---|---|
[React] StateAirline Client (2) : Effect Hook (0) | 2023.04.03 |
[React] State & Props로 간단한 트위터 추가 기능 구현하기 (0) | 2023.04.02 |
[React] 데이터 흐름 & State 끌어올리기 (Lifting State Up) (0) | 2023.04.01 |
[React] Goodbye create-react-app! (0) | 2023.03.31 |