[CodeStates-Section2]U9.React 클라이언트 Ajax 요청

hameee·2022년 12월 7일
0

CodeStates_Frontend_42기

목록 보기
22/39

1.후기

  시간이 지나고 배운 내용이 많아지면서 이전 내용을 까먹었다. 제대로 실습하지 못하고 넘어간 것도 있는데 지금 와서 응용 실습을 해야 하니 꽤 헤맸던 것 같다. 그래도 레퍼런스를 참고하지 않고 모든 테스트를 통과해서 기쁘다.

2.새롭게 알게 된 것

Chapter1. React 데이터 흐름
 -1. React 데이터 흐름
 -2. State 끌어올리기 (Lifting State Up)
Chapter2. Effect Hook
 -1. Side Effect
 -2. Effect Hook 기본
 -3. Effect Hook 조건부 실행
 -4. 컴포넌트 내에서의 AJAX 요청

<Chapter1. React 데이터 흐름>

1.React 데이터 흐름

1) 데이터의 흐름
-단방향, 하향식

-데이터를 전달받을 경우
알 수 있는 것: 데이터의 형태, 타입
모르는 것: state로부터 왔는지, 하드코딩으로 입력한 내용인지

2) React로 사고하기(5단계)
1단계. UI를 컴포넌트 계층 구조로 나누기
단일 책임 원칙: 하나의 컴포넌트는 한 가지 일을 하는 것이 이상적

2단계. React로 정적인 버전 만들기
state 사용❌, 앱을 만들 때는 상향식으로

3단계. UI state에 대한 최소한의 (하지만 완전한) 표현 찾아내기
중복 배제 원칙: 최소한의 state를 찾고, 나머지는 필요에 따라 그때그때 계산되도록 함

ex. TODO 리스트를 만들기
TODO 아이템을 저장하는 배열 -> state✅
TODO 아이템의 개수 -> state❌(TODO 아이템 배열의 길이를 가져오면 됨)

ex. 1단계 이미지 예시
유저가 입력한 검색어, 체크박스의 값 -> state✅
제품의 원본 목록, 필터링된 제품들의 목록 -> state❌

4단계. State가 어디에 있어야 할지 찾기
state를 기반으로 렌더링 하는 모든 컴포넌트 찾음
-> 공통 소유 컴포넌트 찾음
-> 공통 혹은 더 상위에 있는 컴포넌트가 state를 가져야 함
-> state를 소유할 적절한 컴포넌트를 찾지 못하였다면, state를 소유하는 컴포넌트를 하나 만들어서 공통 소유 컴포넌트의 상위 계층에 추가

5단계. 역방향 데이터 흐름 추가하기(상태 끌어올리기)
상위 컴포넌트의 "상태를 변경하는 함수" 그 자체를 하위 컴포넌트로 전달하고, 이 함수를 하위 컴포넌트가 실행(단방향 데이터 흐름의 원칙에 부합)

2.State 끌어올리기

import React, { useState } from "react";

export default function ParentComponent() {
  const [value, setValue] = useState("날 바꿔줘!");

  const handleChangeValue = () => {
    setValue("보여줄게 완전히 달라진 값");
  };

  return (
    <div>
      <div>값은 {value} 입니다</div>
      <ChildComponent handleButtonClick={handleChangeValue} />
    </div>
  );
}

// 자식 컴포넌트에 상태 변경 함수를 실행시키는 함수 전달(구조 분해 할당)
function ChildComponent({ handleButtonClick }) {
  const handleClick = () => {
    handleButtonClick();
  };
  
  // 자식 컴포넌트에서 함수 실행(handleClick -> handleButtonClick -> handleChangeValue -> setValue)
  return <button onClick={handleClick}>값 변경</button>;
}

<Chapter2. Effect Hook>

-> 컴포넌트의 렌더링 이후에 다양한 side effects를 표현할 수 있음(컴포넌트 안의 return 문이 main effect라고 할 수 있음)

1.Side Effect

1) Side Effect (부수 효과)
: 함수 내에서 어떤 구현이 함수 외부에 영향을 끼치는 경우
ex. 컴포넌트 내에서 fetch를 사용해 API 정보를 가져오는 경우, 이벤트를 활용해 DOM 직접 조작하는 경우

2) Pure Function (순수 함수)
: 부수 효과(내->외)가 없고 전달 인자와 상과 없이 항상 같은 값을 리턴(외->내)하는 함수. 즉, 함수 내부가 외부에, 외부가 내부에 영향을 끼치지 않음.

ex.
toUpperCase() -> 순수 함수✅
Math.ramdom() -> 순수 함수❌(동일한 입력에도 다른 출력이 나올 수 있음)
Ajax를 요청하는 함수 -> 순수 함수❌

ex.

// 순수 함수❌
const obj = {value: 12};
function sum(obj, num){// 함수 내부에서 obj 변수 선언
	obj.value += num;// 전달 인자로 들어온 객체의 값을 변경
}

// 순수 함수✅
const obj = {value: 12};
function sum(obj, num){// 함수 내부에서 obj 변수 선언
	return {value: obj.value + num};// 새로운 객체 반환
}

-React 컴포넌트에서의 Side Effect 예시
타이머 사용 (setTimeout), 데이터 가져오기 (fetch API, localStorage)

2.Effect Hook 기본

1) useEffect(렌더링 후 실행되어야 할 햠수(=effect 함수), 종속성 배열)
-기능
React에게 컴포넌트가 렌더링 이후에 어떤 일을 수행해야 하는지를 알려줌

-effect 함수 실행 조건

  • 두 번째 인자 없을 경우: 매번 새롭게 컴포넌트가 렌더링 될 때 실행. 즉, 컴포넌트가 처음 렌더링 되고 난 후 실행, props가 업데이트 될 때 실행, state가 업데이트될 때 실행.
  • 두 번째 인자 있을 경우: 컴포넌트가 처음 렌더링 되고 난 후 실행, state가 업데이트 될 때 실행
  • 두 번째 인자가 빈 배열일 경우: 컴포넌트가 처음 렌더링 되고 난 후 실행

-Hook 사용 시 주의점

  • 최상위에서만 Hook을 호출. 반복문, 조건문 내부에서 호출하지 않음.(컴포넌트가 렌더링 될 때마다 항상 동일한 순서로 Hook이 호출되는 것이 보장하기 위해)

  • React 함수 내에서 Hook을 호출(컴포넌트의 모든 상태 관련 로직을 소스코드에서 명확하게 보이도록 하기 위해)

3.컴포넌트 내에서의 AJAX 요청

1) 목록 내 필터링 구현
방법1. 컴포넌트 내에서 필터링
: 전체 목록 데이터를 불러오고, 목록을 검색어로 filter 하는 방법

import { useEffect, useState } from "react";
import "./styles.css";
import { getProverbs } from "./storageUtil";

export default function App() {
  const [proverbs, setProverbs] = useState([]);
  const [filter, setFilter] = useState("");
  
  // 두 번째 인자가 빈 배열이므로 처음 한 번만 effect 실행
  useEffect(() => {
    console.log("언제 effect 함수가 불릴까요?");
    const result = getProverbs();
    setProverbs(result);
  }, []);

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

  return (
    <div className="App">
      필터
      <input type="text" value={filter} onChange={handleChange} />
      <ul>
    	// 필터링은 여기서 함
        {proverbs
          .filter((prvb) => {
            return prvb.toLowerCase().includes(filter.toLowerCase());
          })
          .map((prvb, i) => (
            <Proverb saying={prvb} key={i} />
          ))}
      </ul>
    </div>
  );
}

function Proverb({ saying }) {
  return <li>{saying}</li>;
}

방법2. 컴포넌트 외부에서 필터링
: 컴포넌트 외부로 API 요청을 할 때, 필터링 한 결과를 받아오는 방법

import { useEffect, useState } from "react";
import "./styles.css";
import { getProverbs } from "./storageUtil";

export default function App() {
  const [proverbs, setProverbs] = useState([]);
  const [filter, setFilter] = useState("");
  const [count, setCount] = useState(0);
  
  // filter가 변할 때마다 effect 실행되면서 필터링 된 결과 렌더링
  useEffect(() => {
    console.log("언제 effect 함수가 불릴까요?");
    const result = getProverbs(filter);
    setProverbs(result);
  }, [filter]);

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

  const handleCounterClick = () => {
    setCount(count + 1);
  };

  return (
    <div className="App">
      필터
      <input type="text" value={filter} onChange={handleChange} />
      <ul>
        {proverbs.map((prvb, i) => (
          <Proverb saying={prvb} key={i} />
        ))}
      </ul>
      <button onClick={handleCounterClick}>카운터 값: {count}</button>
    </div>
  );
}

function Proverb({ saying }) {
  return <li>{saying}</li>;
}

-장단점

2) AJAX 요청을 보내기

useEffect(() => {
  fetch(`http://서버주소/proverbs?q=${filter}`)
    .then(resp => resp.json())
    .then(result => {
      setProverbs(result);
    });
}, [filter]);

3) AJAX 요청이 매우 느릴 경우
외부 API 접속이 느릴 경우를 고려하여, 로딩 화면 구현은 필수

-로딩 화면 구현

const [isLoading, setIsLoading] = useState(false);

// 생략, LoadingIndicator 컴포넌트는 별도로 구현했음을 가정
return {isLoading ? <LoadingIndicator /> : <div>로딩 완료 화면</div>}

-fetch 요청의 전후로 setIsLoading을 설정해 주면 보다 나은 UX를 구현 가능

useEffect(() => {
  setIsLoading(true);// 로딩 화면
  fetch(`http://서버주소/proverbs?q=${filter}`)
    .then(resp => resp.json())
    .then(result => {
      setProverbs(result);
      setIsLoading(false);// 로딩 완료 화면
    });
}, [filter]);

<기타>

1.Batch(=일괄 처리)와 상태 변경 함수

1) batch 의미
더 나은 성능을 위해 여러 개의 state 업데이트를 하나의 리렌더링으로 묶는 것. 리액트는 batch update를 16ms 단위로 진행.(일정 시간 동안 변화하는 상태를 모아 한 번에 렌더링 함)

2) 리액트 버전에 따른 batch 기능 지원 함수
React v17.0: 이벤트 핸들러, useEffect
React v18.0: 이벤트 핸들러, useEffect, Promise 내부, setTimeout

3) 상태 변경 함수와 batch
상태 변경 함수는 비동기적으로 작동하고, 이벤트 핸들러 안에 들어가면 batch 기능이 적용됨. 즉, 상태 변경 함수를 연속 호출하면 모두 취합(batch)한 후에 한 번에 렌더링 함. 그러므로 가장 마지막 상태 변경 함수만 실행된 것과 같이 동작함.(state도 결국 객체이기 때문에, 같은 키값(렌더링 1회)을 가진 경우라면 가장 마지막 실행 값(마지막 상태 변경 함수)으로 덮어씌워짐)

참고 사이트: https://velog.io/@zuzokim/React-setState%EA%B0%80-%EB%B9%84%EB%8F%99%EA%B8%B0%EC%9D%B8-%EC%9D%B4%EC%9C%A0

4) 상태 변경 함수를 동기적으로 실행하는 방법
인자로 함수 넣기, useEffect의 의존성 배열 활용

// 이벤트 핸들러 + 상태 변경 함수1
const eventHandler = () => {
  setCount(count + 1); // 무시
  setCount(count + 1); // 무시
  setCount(count + 1); // count(파라미터) = 초깃값 = 0
} // count = 1

// 이벤트 핸들러 + 상태 변경 함수2
const eventHandler = () => {
  setCount(count + 1); // 무시
  setCount(count + 2); // 무시
  setCount(count + 3); // count(파라미터) = 초깃값 = 0
} // count = 3

// 인자에 함수 넣기1
const eventHandler = () => {
  setCount((prev) => prev + 1); // count = 0 = prev(파라미터)
  setCount((prev) => prev + 1); // count = 1 = prev(파라미터)
  setCount((prev) => prev + 1); // count = 2 = prev(파라미터)
} // count = 3
  
// 인자에 함수 넣기2
const eventHandler = () => {
  setCount((prev) => prev + 1); // count = 0 = prev(파라미터)
  setCount((prev) => prev + 2); // count = 1 = prev(파라미터)
  setCount((prev) => prev + 3); // count = 3 = prev(파라미터)
} // count = 6

2.Fragment

DOM에 별도의 노드를 추가하지 않고 여러 자식 그룹화 가능(의미 없는 <div> 제거 가능), key를 속성으로 가질 수 있음.

참고 사이트: https://ko.reactjs.org/docs/fragments.html#gatsby-focus-wrapper

3.React 엘리먼트, 컴포넌트

엘리먼트: React의 가장 작은 단위
컴포넌트: 여러 엘리먼트와 함수들이 모여 있는 기능적 단위, UI를 재사용 가능한 개별적인 여러 조각으로 나눠놓은 것(컴포넌트도 엘리먼트에 속함)

4.window, localStorage

// 브라우저가 렌더링 되면 localStorage에 {flight: JSON.stringify(flightList)}를 저장하라
if (typeof window !== 'undefined') {
	localStorage.setItem('flight', JSON.stringify(flightList))
}

// 브라우저가 렌더링 되면 localStorage에서 키 'flight'의 값을 가져와라
if (typeof window !== 'undefined') {
	json = localStorage.getItem('flight')
}

5.fetch url에 변수 넣기

// ${}로 변수 삽입(filterBy는 객체)
fetch(`http://ec2-13-124-90-231.ap-northeast-2.compute.amazonaws.com:81/flight?departure=${filterBy.departure}`)

6.함수 매개변수 디폴트 값 설정

값이 없을 경우, undefined이 경우 -> 기본값으로 초기화

// 매개변수 filterBy에 빈 객체를 디폴트 값으로 설정
function getFlight(filterBy = {}) {
  ...
}

7.React Life Cycle

1) Class Component 메서드
최초 렌더링 전 -> componentWillMount()
최초 렌더링 후 -> componentDidMount()

리렌더링 전 -> componentWillUpdate(nextProps, nextState)
리렌더링 후 -> componentDidUpdate(prevProps, prevState)

컴포넌트 제거 시 -> componentWillUnmount()

2) Function Component 메서드
함수 컴포넌트에서 위의 함수와 비슷한 기능을 하는 함수 -> useEffect()
하지만 useEffect는 렌더링 되기 전에 실행되는 경우는 없음.

componentDidMount(): useEffect(() => {🍎 return *}, [])
componentDidMount() + componentWillUnmount(): useEffect(() => {* return 🍎}, [])
componentDidMount() + componentDidUpdate(prevProps, prevState): useEffect(() => {🍎 return *}), useEffect(() => {🍎 return *}, [*])

참고 사이트: https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
참고 영상: https://youtube.com/playlist?list=PLuHgQVnccGMCEfBwnNGsJCQDiqSWI-edj

8.과제 컴포넌트 구조

9.키보드 관련 이벤트 순서

onKeyDown -> onKeyPress -> onChange -> onKeyUp

10.Query String(쿼리 스트링)

function getFlight(filterBy = {}) {
  let queryString = '';
  // 출발지가 있을 경우
  if (filterBy.departure) {
    queryString = queryString + `?departure=${filterBy.departure}`;
  }
  // 도착지가 있을 경우
  if (filterBy.destination) {
    queryString = queryString + `&destination=${filterBy.destination}`;
  }

  return fetch(
    `http://ec2-13-124-90-231.ap-northeast-2.compute.amazonaws.com:81/flight${queryString}`
  ).then((resp) => {
    return resp.json();
  });

11.과제 async/await로 fetch 불러오기

(위의 코드는 프로미스 사용)

export async function getFlight(filterBy = {}) {
  let queryString = '';
  if (filterBy.departure) {
    queryString = queryString + `?departure=${filterBy.departure}`;
  }
  if (filterBy.destination) {
    queryString = queryString + `&destination=${filterBy.destination}`;
  }
  let fetchUrl = await fetch(
    `http://ec2-13-124-90-231.ap-northeast-2.compute.amazonaws.com:81/flight${queryString}`
  );
  let fetchUrltoJS = await fetchUrl.json();

  return fetchUrltoJS;
}

3.보충할 내용

1) 상태 변경 함수는 동기 or 비동기?
2) 상태 병경 함수(함수) 즉, 동기식으로 작성된 상태 변경 함수가 연달아 3개 있을 때 렌더링을 3번 하는가, 1번 하는가?

0개의 댓글