첫번째 테스트는 state & props를 활용한 useState 구문으로 해결할 수 있고, 두번째 테스트는 SideEffect ( 함수 내에서 어떤 구현이 함수 외부에 영향을 끼치는 경우 ex --- 지역변수가 전역변수를 변경시킬 때 ) 를 UseEffect 구문으로 해결한다.
UseEffect 활용에는 2가지 방법이 있다.
빈 배열 넣기
useEffect(함수, [])
아무것도 넣지 않기 (기본 형태)
useEffect(함수)
(2번) 기본 형태의 useEffect는 state가 업데이트 될 때마다 실행된다. 즉, 다중 실행된다.
반면에 (1번) 빈 배열을 useEffect의 두 번째 인자로 사용하면, 이 때에는 컴포넌트가 처음 생성될때만 effect 함수가 실행된다. 즉, 컴포넌트 생성시 1번 실행된다.
이것이 언제 필요할까? 대표적으로 처음 단 한 번, 외부 API를 통해 리소스를 받아오고 더이상 API 호출이 필요하지 않을 때에 사용할 수 있다. 즉, AJAX ( SPA 방식 ) 사용시에 적용할 수 있다.
정리하면 1번 방식을 쓸 때는 CSR 방식을 사용하는 것이고 2번 방식은 SSR 방식을 사용하는 것이다.
AJAX의 가장 큰 특징은 웹 페이지에 필요한 부분에 필요한 데이터만 비동기적으로 받아와 화면에 그려낼 수 있다는 것
브라우저는 Fetch가 서버에 요청을 보내고 응답을 받을때까지 모든 동작을 멈추는 것이 아니라, 계속해서 페이지를 사용할 수 있게 만들어준다
링크텍스트 -- SSR & CSR
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 json from '../resource/flightList'
export default function Main() {
const [condition, setCondition] = useState({ // condition의 초기값은 departure : 'ICN'이다. condition은 상태 변수이다
departure: 'ICN'
})
const [flightList, setFlightList] = useState() // 더이상, 하드코딩된 flightList JSON을 사용하지 않습니다 (초기값은 빈 배열로 둡니다)
const [isLoading, setIsLoading ] = useState(false) // 로딩 상태 설정. 초기값은 false (isloading = false == 초기에는 로딩중이지 않다)
const search = ({ departure, destination }) => {
if (condition.departure !== departure || condition.destination !== destination) {
console.log('condition 상태를 변경시킵니다')
setCondition({ departure, destination }) /// 상태를 변경시켜야 하므로 search의 인자 {departure, destination}를 받아 setCondition을 통해
// TODO: /// condition을 변경시킨다
}
}
useEffect(() => { // 외부에서 호출한 값 (node.js 환경 )으로 내부 상태값 (react 환경 ) 을 변경시켜야 하니 Side Effect로 볼 수 있음. (로컬 함수의 제공값을 이용하는게 아니라 서버의 데이터를 이용함) Side Effect는 Effect Hook으로 처리
setIsLoading(true) // useEffect가 호출되면 데이터를 받아와야 하니 false였던 isLoading 상태를 true로 변경시켜준다
getFlight(condition) // import한 getFlight에서 condition 객체로 필터링을 진행하여 데이터를 추출한 후
.then(data => { // 그 데이터(departure, destination)를 이용해 Flightlist와 isloading의 상태를 변경시켜준다
setFlightList(data) // flightList에는 받아온 데이터를 넘겨주어 상태를 변경시켜주고
setIsLoading(false) // 데이터를 받아왔으니 로딩 화면도 종료시켜준다.
})
}, [condition]) // *useEffect의 두번째 인자 [condition]이 변경될 때마다 앞의 콜백 함수가 실행됨. 참고로 두번째 인자를 []로 두면 컴포넌트가 처음 생성될 때만 실행됨
// 필터함수 삭제
global.search = search // 실행에는 전혀 지장이 없지만, 테스트를 위해 필요한 코드입니다. 이 코드는 지우지 마세요!
return (
<div>
<Head>
<title>States Airline</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<main>
<h1>
여행가고 싶을 땐, States Airline
</h1>
<Search onSearch={search}/> {/*// 상태 변경 함수 `search`가 `onSearch` props로 전달되어야 합니다 */}
<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)} />
필터함수 삭제 & LoadingIndicator 사용 */}
{isLoading ? <LoadingIndicator /> : <FlightList list ={flightList} /> /// 로딩중이면 로딩인디케이터, 아니면 FlightList의 list 항목 추출
}
</div>
<div className="debug-area">
<Debug condition={condition} />
</div>
</main>
</div>
)
}
Search.js
import { includeStack } from 'chai/lib/chai/config'
import { useState } from 'react'
function Search({onSearch}) { // 상태 변경 함수 `search`가 `onSearch` props로 전달되어야 합니다. 함수로 받기 위해 {}로 받음 주의?
const [textDestination, setTextDestination] = useState('')
//const [textDeparture, setTextDeparture] = useState('')
const handleChange = (e) => { /// 받아온 인자값을 대문자로 만든다
setTextDestination(e.target.value.toUpperCase())
}
//const handleChange2 = (e) => {
// setTextDeparture(e.target.value.toUpperCase())}
const handleKeyPress = (e) => { // 엔터 버튼이 눌렸는지 감지하는 이벤트
if (e.type === 'keypress' && e.code === 'Enter') { // 키가 눌렸고 그게 엔터라면 핸들서치클릭을 실행
handleSearchClick()
}
}
const handleSearchClick = () => {
console.log('검색 버튼을 누르거나, 엔터를 치면 search 함수가 실행됩니다')
// TODO: 상태 변경 함수 `search`는 Search 컴포넌트의 `검색` 버튼 클릭 시 실행되어야 합니다
// search는 setCondition({ departure, destination })를 실행
// 검색 버튼 클릭시 >> onClick >> {handleSearchClick}
onSearch({departure : "ICN", destination : textDestination}) // 버튼이 클릭되면 onSearch의 값이 search 함수에게 전달되어 실행
}
return <fieldset>
<legend>공항 코드를 입력하고, 검색하세요</legend>
<span>출발지</span> {/* 출발지는 ICN 고정*/}
<input id="input-departure" type="text" disabled value="ICN"></input>
{/*<input id="input-departure"
type="text"
value={textDeparture}
onChange={handleChange2}
placeholder="CJU, BKK, PUS"
onkeyPress={handleKeyPress}></input>
이 코드는 departure를 ICN 고정을 해체하여 선택 변경 가능하게 만드는 코드. 위의 주석들과 같이 해체하고 기존의 코드를 주석처리하면 변경 가능.
*/}
<span>도착지</span> {/*도착지 입력란. 이벤트 함수는 3가지. handleSearchClick, handleChange, handleKeyPress */}
<input id="input-destination" type="text" value={textDestination} onChange={handleChange} placeholder="CJU, BKK, PUS 중 하나를 입력하세요" onKeyPress={handleKeyPress} />
<button id="search-btn" onClick={handleSearchClick}>검색</button>
</fieldset>
}
export default Search
FlightDataAPI.js
import flightList from '../resource/flightList'
import fetch from 'node-fetch'
if (typeof window !== "undefined") {
localStorage.setItem('flight', JSON.stringify(flightList));
}
export function getFlight(filterBy = {}) {
// HINT: 가장 마지막 테스트를 통과하기 위해, fetch를 이용합니다. 아래 구현은 완전히 삭제되어도 상관없습니다.
// TODO: 아래 구현을 REST API 호출로 대체하세요.
// 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);
// });
// return fetch(url)가 필요한데 url에 들어갈 값은 필터링된 데이터의 값이다.
let result = '' // result 변수 선언
if(filterBy.departure){ // filterBy.departure가 호출되면 즉 export한 getFlight이 호출되면 (Main 컴포 참고 --- getFlight(condition))
result = result + `departure=${filterBy.departure}&` // result에 필터링된 daparture를 json화시켜 저장
}
if(filterBy.destination){ // filterBy.destination이 호출되면 즉 export한 getFlight이 호출되면 (Main 컴포 참고 --- getFlight(condition))
result = result + `destination=${filterBy.destination}` // result에 필터링된 destination도 json화시켜 저장
}
let url = `http://ec2-13-124-90-231.ap-northeast-2.compute.amazonaws.com:81/flight?${result}` // 필터링된 departure와 destination의 값을 담은 result를 명시한 url 설정
return fetch(url) // url을 서버에 요청한다
.then(res => res.json()) // 받아온 데이터를 json화 시켜 저장한다
}