React 클라이언트 Ajax 요청
이전 학습에서 여러분은 React의 기본적인 개념들에 대해서 배우는 시간을 가져보았습니다.
이번 유닛에서는 React 데이터 흐름에 대해서 다시 한번 알아보고, 여러 컴포넌트 사이에서 어떤 방식으로 데이터들을 다루게 되는지에 대한 방법을 배웁니다. 또, Effect Hook과 Ajax를 사용해 서버로부터 데이터를 받아오는 방법에 대해서 학습합니다.
Chapter1. React 데이터 흐름
Chapter2. Effect Hook
과제
- StateAirline Client: 학습한 내용을 바탕으로 과제를 수행합니다.
Chapter1 - React 데이터 흐름
Chapter1-1. React 데이터 흐름
- 개념학습 : React 데이터 흐름에 대해 이해합니다.
Chapter1-2. State 끌어올리기 (Lifting State Up)
- 개념학습 : State 끌어올리기에 대해 이해합니다.
학습 목표
- React에서의 데이터 흐름, 단방향 데이터 흐름을 이해할 수 있다.
- 어떤 컴포넌트에 state가 위치해야 하는지 알 수 있다.
- State 끌어올리기의 개념을 이해할 수 있다.
- 상태 변경 함수가 정의된 컴포넌트와, 상태 변경 함수를 호출하는 컴포넌트가 다름을 알 수 있다.
Chapter1-1. React 데이터 흐름
Action Items
위 슬라이드를 다 보고 난 후 React 공식 문서의 주요 개념 중 다음 주제를 반드시 읽어야 합니다.
- 5. State와 생명주기 중
- 데이터는 아래로 흐릅니다
- 12. React로 생각하기
슬라이드 및 공식 문서에 등장하는 모든 예제를 codesandbox에서 직접 실습해 보세요.
Chapter1-2. State 끌어올리기 (Lifting State Up)
Chapter1-1. 개념학습에서 언급된 상태 끌어올리기에 대해 좀 더 자세히 알아보도록 하겠습니다.
단방향 데이터 흐름이라는 원칙에 따라, 하위 컴포넌트는 상위 컴포넌트로부터 전달받은 데이터의 형태 혹은 타입이 무엇인지만 알 수 있습니다. 데이터가 state로부터 왔는지, 하드코딩으로 입력한 내용인지는 알지 못합니다.
그러므로 하위 컴포넌트에서의 어떤 이벤트로 인해 상위 컴포넌트의 상태가 바뀌는 것은 마치 "역방향 데이터 흐름"과 같이 조금 이상하게 들릴 수 있습니다. React가 제시하는 해결책은 다음과 같습니다.
상위 컴포넌트의 "상태를 변경하는 함수" 그 자체를 하위 컴포넌트로 전달하고, 이 함수를 하위 컴포넌트가 실행한다
여전히 단방향 데이터 흐름의 원칙에 부합하는 해결 방법입니다. 바로 이것이 "상태 끌어올리기"입니다.
예제
예제 앱은 부모와 자식 컴포넌트가 하나씩 존재하는 트리 구조입니다. 그리고, 상태를 변경시킬 수 있는 메서드가 존재한다고 생각해 보세요. 다음과 같은 구성을 가지게 될 것입니다.
import React, { useState } from "react";
// 부모 컴포넌트
export default function ParentComponent() {
// 부모 컴포넌트가 가지는 상태와 상태 변경 함수
const [value, setValue] = useState("날 바꿔줘!");
// 부모의 상태를 변경하는 함수 재 정의
// 재 정의 하는 이유는 전달받은 값(newValue)로 value를 변경하기 위해서
// 함수로 한 번 더 감쌈.
const handleChangeValue = (newValue) => {
setValue(newValue);
};
// 리엑트에서 반환하는 화면(JSX 활용)
return (
<div>
<div>값은 {value} 입니다</div>
<ChildComponent handleButtonClick={handleChangeValue} />
</div>
);
}
// 자식 컴포넌트
// 부모에게서 내려받은 props를 구조분해할당을 통해 사용
function ChildComponent({ handleButtonClick }) {
// 자식 컴포넌트에서 사용할 state 선언
const [inputValue, setInputValue] = useState("");
// 자식 컴포넌트에서 inputBox의 값이 변경될 때마다
// inputValue를 변경하기 위한 함수 생성
const handleInputChange = (event) => {
setInputValue(event.target.value);
};
// 버튼을 클릭했을때, 부모로 부터 전달받은 callback함수를 통해
// 부모 컴포넌트의 value를 변경하기 위한 함수
const handleClick = () => {
handleButtonClick(inputValue);
};
return (
<div>
<input type="text" value={inputValue} onChange={handleInputChange} />
<button onClick={handleClick}>값 변경</button>
</div>
);
}
callback 다시보기
콜백(callback)은 다른 함수(고차 함수)의 인자로 전달되는 함수를 의미합니다.
// 고차함수
function each(array, iterator) {
for(let i = 0; i < array.length; i++) {
let element = array[i]
iterator(element, i, array)
}
}
// 콜백 함수
function printElement(element) {
console.log(element)
}
each(['hello', 'world'], printElement);
앞서 React의 해결책은 다음과 같다고 말했습니다.
상위 컴포넌트의 "상태를 변경하는 함수" 그 자체를 하위 컴포넌트로 전달하고, 이 함수를 하위 컴포넌트가 실행한다
다시 React 예제로 돌아가서, 상태를 변경하는 함수는 무엇인가요? 하위 컴포넌트로는 어떻게 전달할 수 있을까요?
상태를 변경하는 함수는 handleChangeValue입니다. 전달은 props를 이용합시다. props 이름은 적절하게 지어줍시다. 하위 컴포넌트가 버튼 클릭 이벤트에 따라 상태를 변경하려고 하므로 이름은 handleButtonClick이라고 지어줍시다.
function ParentComponent() {
const [value, setValue] = useState("날 바꿔줘!");
const handleChangeValue = () => {
setValue("보여줄게 완전히 달라진 값");
};
return (
<div>
<div>값은 {value} 입니다</div>
<ChildComponent handleButtonClick={handleChangeValue} />
</div>);
}
<ChildComponent> 는 마치 고차 함수가 인자로 받은 함수를 실행하듯, props로 전달받은 함수를 컴포넌트 내에서 실행할 수 있게 됩니다. "상태 변경 함수"는 버튼이 클릭할 때 실행되기를 원하므로, 해당 부분에 콜백 함수를 실행합시다.
function ChildComponent({ handleButtonClick }) {
const handleClick = () => {
// Q. 이 버튼을 눌러서 부모의 상태를 바꿀 순 없을까?
// A. 인자로 받은 상태 변경 함수를 실행하자!
handleButtonClick()
}
return (
<button onClick={handleClick}>값 변경</button>)
}
필요에 따라 설정할 값을 콜백 함수의 인자로 넘길 수도 있습니다.
function ParentComponent() {
const [value, setValue] = useState("날 바꿔줘!");
const handleChangeValue = (newValue) => {
setValue(newValue);
};
// ...생략...
}
function ChildComponent({ handleButtonClick }) {
const handleClick = () => {
handleButtonClick('넘겨줄게 자식이 원하는 값')
}
return (
<button onClick={handleClick}>값 변경</button>)
}
보다 실용적인 예제를 공식 문서에서 찾을 수 있습니다.
이 원리를 통해 컴포넌트 간 데이터 교환을 성공적으로 구현해 냅시다.
Action Item: Twittler 예제 분석하기
앞서 React 데이터 흐름 슬라이드에 등장한 Twittler의 소스코드가 있습니다. 컴포넌트 구성과 상태 및 상태 변경 함수가 준비되어 있습니다. 어떻게 <NewTweetForm> 컴포넌트에서 tweets 상태를 변화시킬 수 있을까요? 어떻게 State 끌어올리기를 적용할 수 있을까요?
[과제] StatesAirline Client - Part 1
StateAirline Client
주어진 StatesAirline Client 앱은 아직 미완성 상태입니다. 앞에서 배운 상태 끌어올리기, 데이터 흐름 개념을 활용하여 항공편 검색 기능을 구현합니다. 이번 과제의 핵심은 네트워크 요청을 통해 항공편 리스트를 받아오고, 도착지 정보 검색 기능을 구현하는 것입니다. 이 기능 구현을 위해서 상태 변경 함수를 어디로 전달할지, Effect hook을 어떻게 활용할 수 있을지를 고민해 봅시다.
과제 - StatesAirline Client
학습 목표
- React의 데이터 흐름에 대해 이해하고 state를 전달할 수 있다.
- 상태 끌어올리기를 활용하여, 원하는 컴포넌트에서 state를 변경할 수 있다.
- Side Effect의 개념에 대해서 이해할 수 있다.
- Effect Hook을 활용하여 비동기 호출 및 Ajax 요청과 같은 side effect를 처리할 수 있다.
- 네트워크 요청이 느릴 경우에 표시되는 로딩 화면을 구현할 수 있다.
시작하기
Bare Minimum Requirement
- Part 1의 모든 테스트를 통과합니다.
- 검색 조건을 변경하면, 해당 검색 조건에 맞는 항공권이 필터링되어 목록에 표시되게 만듭니다.
Getting Started
- StatesAirline Client Repository 주소
- npm run start를 이용해 먼저 앱을 띄워보고, 코드를 훑어보세요!
- npm run server를 이용해 먼저 로컬에서 서버를 띄워야 합니다.
- npm run test를 이용해 테스트를 실행할 수 있습니다.
디렉토리 구조
.
├── 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 # 스타일 시트
1. 제공되는 코드를 통해 상태 끌어올리기를 이해합시다.
이번 과제에서는 상태 관리 연습과 함께, 상태 끌어올리기를 직접 구현해 봅니다. 먼저 Search 컴포넌트는 필터링 조건을 업데이트하는 액션을 보냅니다. React의 데이터 흐름 방식에 따라 "필터링 조건(condition)"이라는 상태는, Main 컴포넌트에 위치하며, 이에 따라 FlightList에 영향을 줍니다. 이 흐름의 전반이 바로 상태 끌어올리기입니다.
!https://s3.ap-northeast-2.amazonaws.com/urclass-images/bkkLOx8Hq-1621826422394.png
- Search에 존재하는 버튼 클릭이 Main의 상태 변화를 만듭니다.
- Main의 상태 변화는 FlightList의 목록을 업데이트합니다.
자 이해가 끝났다면, 테스트를 통과하러 갈 차례입니다. Part 1 테스트를 전부 통과하세요.
- 추가할 파일
module.exports = { presets: [ '@babel/preset-env', // 필요한 다른 프리셋들 ['@babel/preset-react', { runtime: 'automatic' }] // JSX 변환을 위한 프리셋에 runtime 옵션 추가 ], plugins: [ // 필요한 다른 플러그인들 ] };
- babel.config.js
- 터미널에서 아래 명령어 복사하여 실행
- npm install --save-dev @babel/preset-env @babel/preset-react\
Chapter2 - Effect Hook
앞에서는 React 컴포넌트 내부의 데이터 흐름에 대해서 알아보았습니다. 지금부터는 React 컴포넌트 외부에서 데이터를 처리하고 받아오는 과정을 배워봅시다.
Chapter2-1. Side Effect
- 개념학습 : Side Effect의 개념에 대해 이해합니다.
- 퀴즈 : Side Effect에 대한 이해도를 퀴즈를 통해 점검합니다.
Chapter2-2. Effect Hook 기본
- 개념학습 : Effect Hook의 개념에 대해 이해합니다.
- 퀴즈 : Effect Hook에 대한 이해도를 퀴즈를 통해 점검합니다.
Chapter2-3. Effect Hook 조건부 실행
- 개념학습 : Effect Hook 조건부 실행의 개념에 대해 이해합니다.
- 퀴즈 : Effect Hook 조건부 실행에 대한 이해도를 퀴즈를 통해 점검합니다.
Chapter2-4. 컴포넌트 내에서의 AJAX 요청
- 개념학습 : 컴포넌트 내에서의 AJAX 요청의 개념에 대해 이해합니다.
- 퀴즈 : 컴포넌트 내에서의 AJAX 요청에 대한 이해도를 퀴즈를 통해 점검합니다.
학습 목표
- Side effect가 어떤 의미인지 알 수 있다.
- React 컴포넌트를 만들 때 side effect로부터 분리해서 생각할 수 있다.
- Side effect의 예를 들 수 있다.
- Effect Hook을 이용해 비동기 호출 및 AJAX 요청과 같은 side effect를 React 컴포넌트 내에서 처리할 수 있다.
- Effect Hook에서의 dependency array 사용법을 이해할 수 있다.
- 컴포넌트 내에서 네트워크 요청 시, 로딩 화면과 같이 보다 나은 UI를 만드는 법을 이해할 수 있다.
Chapter2-1. Side Effect
Side Effect (부수 효과)
함수 내에서 어떤 구현이 함수 외부에 영향을 끼치는 경우 해당 함수는 Side Effect가 있다고 이야기합니다. React에서는 컴포넌트 내에서 fetch를 사용해 API 정보를 가져오거나 이벤트를 활용해 DOM 직접 조작할 때 Side Effect가 발생했다고 말합니다.
다음은, 전역 변수 foo를 bar라는 함수가 수정하는 예제입니다.
let foo = 'hello';
function bar() {
foo = 'world';
}
bar(); // bar는 Side Effect를 발생시킵니다!
Pure Function (순수 함수)
순수 함수란, 오직 함수의 입력만이 함수의 결과에 영향을 주는 함수를 의미합니다. 함수의 입력이 아닌 다른 값이 함수의 결과에 영향을 미치는 경우, 순수 함수라고 부를 수 없습니다. 또한 순수 함수는, 입력으로 전달된 값을 수정하지 않습니다.
function upper(str) {
return str.toUpperCase(); // toUpperCase 메소드는 원본을 수정하지 않습니다 (Immutable)
}
upper('hello') // 'HELLO'
순수 함수에는 네트워크 요청과 같은 Side Effect가 없습니다. 순수 함수의 특징 중 하나는, 어떠한 전달 인자가 주어질 경우, 항상 똑같은 값이 리턴됨을 보장합니다. 그래서 예측 가능한 함수이기도 합니다.
질문
- Math.random()은 순수 함수가 아닙니다. 왜일까요?
- 어떤 함수가 fetch API를 이용해 AJAX 요청을 한다고 가정해 봅시다. 이 함수는 순수 함수가 아닙니다. 왜일까요?
React의 함수 컴포넌트
우리가 앞서 배운 React의 함수 컴포넌트는, props가 입력으로, JSX Element가 출력으로 나갑니다. 여기에는 그 어떤 Side Effect도 없으며, 순수 함수로 작동합니다.
function SingleTweet({ writer, body, createdAt }) {
return <div>
<div>{writer}</div>
<div>{createdAt}</div>
<div>{body}</div>
</div>
}
하지만 보통 React 애플리케이션을 작성할 때에는, AJAX 요청이 필요하거나, LocalStorage 또는 타이머와 같은 React와 상관없는 API를 사용하는 경우가 발생할 수 있습니다. 이는 React의 입장에서는 전부 Side Effect입니다. React는 Side Effect를 다루기 위한 Hook인 Effect Hook을 제공합니다.
React 컴포넌트에서의 Side Effect
- 타이머 사용 (setTimeout)
- 데이터 가져오기 (fetch API, localStorage)
Chapter2-2. Effect Hook 기본
Effect Hook
여기 명언을 보여주는 간단한 애플리케이션이 있습니다. 먼저 이 링크를 열어서 버튼을 클릭할 때마다 브라우저 상단의 타이틀이 어떻게 변경되는지 확인해 보세요.
useEffect는 컴포넌트 내에서 Side effect를 실행할 수 있게 하는 Hook입니다. 이 컴포넌트에서 실행하는 Side effect는 브라우저 API를 이용하여, 타이틀을 변경하는 것입니다. 다음 코드를 확인해 보세요.
API
useEffect(함수)
useEffect의 첫 번째 인자는 함수입니다. 해당 함수 내에서 side effect를 실행하면 됩니다. 이 함수는 다음과 같은 조건에서 실행됩니다.
언제 실행되나요?
- 컴포넌트 생성 후 처음 화면에 렌더링(표시)
- 컴포넌트에 새로운 props가 전달되며 렌더링
- 컴포넌트에 상태(state)가 바뀌며 렌더링
이와 같이 매번 새롭게 컴포넌트가 렌더링 될 때 Effect Hook이 실행됩니다.
Hook을 쓸 때 주의할 점
- 최상위에서만 Hook을 호출합니다.
- React 함수 내에서 Hook을 호출합니다.
공식 문서를 통해 이 내용에 대해 더 공부하세요.
Chapter2-3. Effect Hook 조건부 실행
조건부 effect 발생 (dependency array)
useEffect의 두 번째 인자는 배열입니다. 이 배열은 조건을 담고 있습니다. 여기서 조건은 boolean 형태의 표현식이 아닌, 어떤 값의 변경이 일어날 때를 의미합니다. 따라서, 해당 배열엔 어떤 값의 목록이 들어갑니다. 이 배열을 특별히 종속성 배열이라고 부릅니다.
이 예제를 열어보고, 개발자 콘솔의 값을 확인하세요. 여기에는 다음과 같은 세 상태가 존재합니다.
- 명언 목록 (proverbs)
- 필터링할 문자열 (effect)
- 카운트 (count)
이 예제는, filter가 변할 때에만, effect 함수가 실행됩니다. 개발자 콘솔을 통해 확인할 수 있습니다.
한편, 카운트를 올리는 버튼은 컴포넌트의 상태가 바뀌고 업데이트되지만, 아무리 버튼을 눌러도 effect 함수는 실행되지 않습니다. 왜냐하면, 종속성 배열에는 filter만 존재하고, count는 존재하지 않기 때문입니다.
질문
- 카운트 버튼을 눌렀을 때에도 effect 함수를 실행시키려면 어떻게 해야 하나요?
API
useEffect(함수, [종속성1, 종속성2, ...])
useEffect의 두 번째 인자는 종속성 배열입니다. 배열 내의 종속성1, 또는 종속성2의 값이 변할 때, 첫 번째 인자의 함수가 실행됩니다.
배열 내의 어떤 값이 변할 때에만, (effect가 발생하는) 함수가 실행됩니다.
단 한 번만 실행되는 Effect 함수
만일 종속성 목록에 아무런 종속성도 없다면 어떤 일이 발생할까요? 달리 말해, 두 번째 배열을 빈 배열[]로 둘 경우에는 무슨 일이 발생할까요? 두 번째 인자를 아예 안 넘기는 것과 어떻게 다를까요?
빈 배열 넣기
useEffect(함수, [])
2. 아무것도 넣지 않기 (기본 형태)
useEffect(함수)
(2번) 기본 형태의 useEffect는 컴포넌트가 처음 생성되거나, props가 업데이트되거나, 상태(state)가 업데이트될 때 effect 함수가 실행됨을 앞서 배웠습니다.
반면에 (1번) 빈 배열을 useEffect의 두 번째 인자로 사용하면, 이때에는 컴포넌트가 처음 생성될 때만 effect 함수가 실행됩니다. 이것이 언제 필요할까요? 대표적으로 처음 단 한 번, 외부 API를 통해 리소스를 받아오고 더 이상 API 호출이 필요하지 않을 때에 사용할 수 있습니다.