20. [React] 클라이언트 Ajax 요청

문도연·2022년 6월 14일
0

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


Chapter1. React 데이터 흐름

  • React에서의 데이터 흐름, 단방향 데이터 흐름을 이해할 수 있다.
  • 어떤 컴포넌트에 state가 위치해야 하는지 알 수 있다.
  • State 끌어올리기의 개념을 이해할 수 있다.
  • 상태 변경 함수가 정의된 컴포넌트와, 상태 변경 함수를 호출하는 컴포넌트가 다름을 알 수 있다.

1-1. React 데이터 흐름

Props vs State

React에는 두 가지 데이터 “모델”인 props와 state가 있음

props
부모가 자식에게 데이터를 넘겨줄 때 사용할 수 있는 방법

state
오직 상호작용을 위해, 즉 시간이 지남에 따라 데이터가 바뀌는 것에 사용함

  • 어떤 컴포넌트가 state를 변경하거나 소유할지 찾는 게 중요함

단방향 데이터 흐름(one-way data flow)

  • 컴포넌트는 컴포넌트 바깥에서 props를 이용해 데이터를 마치 전달인자(arguments) 혹은 속성(attributes)처럼 전달받을 수 있음
    • 부모 컴포넌트는 자신의 state를 자식 컴포넌트에 props로 전달할 수 있습니다.
<FormattedDate date={this.state.date} />

자식인 FormattedDate 컴포넌트는 date를 자신의 props로 받을 것이고 이것이 Clock(예시에서 부모 컴포넌트)의 state로부터 왔는지, Clock의 props에서 왔는지, 수동으로 입력한 것인지 알지 못합니다.

function FormattedDate(props) {
  return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}
  • 모든 state는 항상 특정한 컴포넌트가 소유하고 있으며 그 state로부터 파생된 UI 또는 데이터는 오직 트리구조에서 자신의 “아래”에 있는 컴포넌트에만 영향을 미칩니다.

역방향 데이터 흐름 추가

  • 부모 컴포넌트에서의 상태가 하위 컴포넌트에 의해 변하는 경우
    • 예시 : 새로운 트윗 추가 -> 부모가 가진 전체트윗목록(state)상태를 변화시킴
    • 즉, 하위 컴포넌트(NewTweetForm)에서의 클릭 이벤트가, 부모의 상태를 바꾸어야만 하는 상황

State 끌어올리기(Lifting state up) 로 해결

상태를 변경시키는 함수(handler)를 하위 컴포넌트에 props로 전달해서 해결할 수 있음

참고

https://ko.reactjs.org/docs/state-and-lifecycle.html

    1. State와 생명주기 중 데이터는 아래로 흐릅니다
    1. React로 생각하기

1-2. State 끌어올리기 (Lifting State Up)

하위 컴포넌트에서의 어떤 이벤트로 인해 상위 컴포넌트의 상태가 바뀌는 것은 마치 "역방향 데이터 흐름"과 같이 조금 이상하게 들릴 수 있긴 하나 아래의 solution 에 의해 React의 단방향 데이터 흐름의 원칙은 항상 부합함.

상위 컴포넌트의 "상태를 변경하는 함수" 그 자체를 하위 컴포넌트로 전달하고, 이 함수를 하위 컴포넌트가 실행한다

state 끌어올리기의 이점

컴포넌트 간 데이터 교환을 원활하게 할 수 있음
앱이 힙해짐

예제 1

state 끌어올리기 하는 방법

  • 상태 갱신 함수는 setValue, 상태를 변경하는 함수는 handleChangeValue
    -> props로 handleChangeValue를 자식 컴포넌트에 전달할 것임
    -> props의 이름은 handelButtonClick로 지어줘
    -> 를 자식 컴포넌트 {인자} 에 넣고
    -> 자식 컴포넌트 내에 를 실행시킬 함수블록 내에 넣어줘

  • handelButtonClick를 총 3번 작성함

부모 컴포넌트

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

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

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

자식 컴포넌트

function ChildComponent({ handleButtonClick }) {
  const handleClick = () => {
    // Q. 이 버튼을 눌러서 부모의 상태를 바꿀 순 없을까?
    // A. 인자로 받은 상태 변경 함수를 실행하자!

    handleButtonClick()
  }

  return (
    <button onClick={handleClick}>값 변경</button>
  )
}

예제 2 - 자식 을 전달하는 방법

부모 컴퍼넌트

import React, { useState } from "react";
import "./styles.css";

const currentUser = "김코딩";

function Twittler() {
  const [tweets, setTweets] = useState([
    {
      uuid: 1,
      writer: "김코딩",
      date: "2020-10-10",
      content: "안녕 리액트"
    },
    {
      uuid: 2,
      writer: "박해커",
      date: "2020-10-12",
      content: "좋아 코드스테이츠!"
    }
  ]);

  const addNewTweet = (newTweet) => {
    setTweets([...tweets, newTweet]);
  }; // 이 상태 변경 함수가 NewTweetForm에 의해 실행되어야 합니다.

  return (
    <div>
      <div>작성자: {currentUser}</div>
      <NewTweetForm onButtonClick={addNewTweet}/> // props로 전달
      <ul id="tweets">   
        {tweets.map((t) => (
          <SingleTweet key={t.uuid} writer={t.writer} date={t.date}>
            {t.content}
          </SingleTweet>
        ))}
      </ul>
    </div>
  );
}

자식 컴포넌트

function NewTweetForm({ onButtonClick }) {
  const [newTweetContent, setNewTweetContent] = useState("");

  const onTextChange = (e) => {
    setNewTweetContent(e.target.value);
  };

  const onClickSubmit = () => {
    let newTweet = {
      uuid: Math.floor(Math.random() * 10000),
      writer: currentUser,
      date: new Date().toISOString().substring(0, 10),
      content: newTweetContent
    };
    onButtonClick(newTweet);
    // TDOO: 여기서 newTweet이 addNewTweet에 전달되어야 합니다.
  };

  return (
    <div id="writing-area">
      <textarea id="new-tweet-content" onChange={onTextChange}></textarea>
      <button id="submit-new-tweet" onClick={onClickSubmit}>  
        새 글 쓰기
      </button>
    </div>
  );
}

자식 컴포넌트

function SingleTweet({ writer, date, children }) {
  return (
    <li className="tweet">
      <div className="writer">{writer}</div>
      <div className="date">{date}</div>
      <div>{children}</div>
    </li>
  );
}

export default Twittler;


Chapter2. Effect Hook

  • Side effect가 어떤 의미인지 알 수 있다.
  • React 컴포넌트를 만들 때 side effect로부터 분리해서 생각할 수 있다. (비즈니스 로직과 표현 영역 구분)
    • Side effect의 예를 들 수 있다.
  • Effect Hook을 이용해 비동기 호출 및 AJAX 요청과 같은 side effect를 React 컴포넌트 내에서 처리할 수 있다.
  • Effect Hook에서의 dependency array 사용법을 이해할 수 있다.
  • 컴포넌트 내에서 네트워크 요청 시, 로딩 화면과 같이 보다 나은 UI를 만드는 법을 이해할 수 있다.

2-1. Side Effect(부수 효과)

Side Effect

  • 함수 내부에서 외부의 값에 관여하는 경우 Side Effect가 있다고 말함
//전역 변수 foo를 bar라는 함수가 수정하는 예제
   let foo = 'hello';

function bar() {
  foo = 'world';
}

bar(); // bar는 Side Effect를 발생시킵니다!

리액트에서의 Side Effect

  • 컴포넌트 내에서 fetch를 사용해 API 정보를 가져오는 경우
  • 이벤트를 활용해 DOM 직접 조작하는 경우
    • Side Effect가 발생했다고 말함

Pure Function (순수 함수)

  • 오직 함수의 입력만이 함수의 결과에 영향을 주는 함수
  • 어떠한 전달 인자가 주어질 경우, 항상 똑같은 값이 리턴됨을 보장하는 함수
    • 예측 가능한 함수
  • 순수 함수는, 입력으로 전달된 값을 수정하지 않음
    • 함수의 입력이 아닌 다른 값이 함수의 결과에 영향을 미치는 경우, 순수 함수가 아님
  • 순수 함수에는 네트워크 요청과 같은 Side Effect가 없음
function upper(str) {
  return str.toUpperCase(); // toUpperCase 메소드는 원본을 수정하지 않습니다 (Immutable)
}

upper('hello') // 'HELLO'

질문

  • Math.random()은 순수 함수가 아닙니다. 왜일까요?

    • Math.ramdom() 같은 메서드는 동일한 입력에도 다른 출력이 나올 수 있으므로 순수 함수가 아닙니다.
  • 어떤 함수가 fetch API를 이용해 AJAX 요청을 한다고 가정해 봅시다. 이 함수는 순수 함수가 아닙니다. 왜일까요?

    • 어떤 함수가 서버에 네트워크 요청을 보낸다면 이로 인해 서버의 데이터에 Side Effect를 일으킬 수 있고, 이에 따라 해당 함수는 순수 함수가 아닙니다.

React의 함수 컴포넌트

React의 함수 컴포넌트는, props가 입력으로, JSX Element가 출력으로 나갑니다. 여기에는 그 어떤 Side Effect도 없으며, 순수 함수로 작동합니다.

React 컴포넌트에서의 Side Effect

  • 타이머 사용 (setTimeout)
  • 데이터 가져오기 (fetch API, localStorage)
    • AJAX 요청이 필요하거나, LocalStorage 또는 타이머와 같은 React와 상관없는 API를 사용하는 경우

Side Effect 관리

  • 관리해야 하는 이유
    • Side Effect를 최소화하거나 따로 분리하여 함수로 묶어주는 작업은 해당 프로젝트나 소프트웨어의 유지보수를 좀 더 수월하게 해주기 때문임
  • 어떻게 관리하나?
    • 리액트에서 사이드이펙트를 다루기 위한 Hook인 Effect Hook을 제공

2-2. Effect Hook 기본

Effect Hook

useEffect는 컴포넌트 내에서 Side effect를 실행할 수 있게 하는 Hook
첫 번째 인자는 함수로, 해당 함수 내에서 side effect를 실행하면 됨

useEffect 3가지 형태

1) 인자로 콜백함수만 있는경우

useEffect(()=>{
 console.log(몇 번 호출될까요?)
})

컴포넌트가 처음 생성되거나, props가 업데이트 되거나, state가 업데이트 될 때마다 실행

2) 인자로 콜백함수+특정state변수

useEffect(()=>{
 console.log(몇 번 호출될까요?)
}, [count])

컴포넌트가 최초 마운트될때(화면에 나타날때), state인 count가 업데이트 될 때마다 실행됩니다.

배열 = Dependency Array

3) 인자로 콜백함수+빈배열

useEffect(()=>{
 console.log(몇 번 호출될까요?)
}, [])

컴포넌트가 처음 생성될 때만 단 한 번만, effect 함수가 실행됩니다.
Effect Hook을 컴포넌트 내에서 최초 1회만 호출하고 싶을 때 사용
ex) 대표적으로 처음 단 한 번, 외부 API를 통해 리소스를 받아오고 더 이상 API 호출이 필요하지 않을 때에 사용할

useEffect 언제 실행되는가?

매번 새롭게 컴포넌트가 렌더링 될 때 Effect Hook이 실행됨

  • 컴포넌트 생성 후 처음 화면에 렌더링(표시)
  • 컴포넌트에 새로운 props가 전달되며 렌더링
  • 컴포넌트에 상태(state)가 바뀌며 렌더링

Hook을 쓸 때 주의할 점

  • 최상위에서만 Hook을 호출합니다.
  • React 함수 내에서 Hook을 호출합니다.

2-3. Effect Hook 조건부 실행

조건부 effect 발생 (dependency array)

useEffect(함수, [종속성1, 종속성2, ...])

  • useEffect의 두 번째 인자는 배열이고 이 배열은 조건을 담고 있음

    • 여기서 조건은 어떤 값의 변경이 일어날 때를 의미함
    • 이 배열엔 어떤 값의 목록이 들어감
    • 이 배열을 종속성 배열이라 부름
  • useEffect의 두 번째 인자는 종속성 배열입니다.

    • Effect hook의 조건을 담은 배열이다.
    • 배열 내의 종속성1, 또는 종속성2의 값이 변할 때, 첫 번째 인자의 함수가 실행됩니다.
    • 배열 내의 어떤 값이 변할 때에만, (effect가 발생하는) 함수가 실행
    • 각 종속성은 표현식이 아닌 어떤 값으로 할당합니다.

2-4. 컴포넌트 내에서의 AJAX 요청

Data Fetching: 명언 목록 필터링 예제

목록 내 필터링을 구현하는 방법 2가지

  • 컴포넌트 내에서 필터링
    • 전체 목록 데이터를 불러오고, 목록을 검색어로 filter 하는 방법
  • 컴포넌트 외부에서 필터링
    • 컴포넌트 외부로 API 요청을 할 때, 필터링 한 결과를 받아오는 방법 (서버에 매번 검색어와 함께 요청하는 경우에 해당)

(1) 컴포넌트 내에서 필터링

처음 단 한 번, 외부 API로부터 명언 목록을 받아오고, filter 함수를 이용

(2) 컴포넌트 외부에서 필터링

검색어가 바뀔 때마다, 외부 API를 호출합니다.

두 방식의 차이점

방식장점단점
컴포넌트 내부에서 처리HTTP 요청의 빈도를 줄일 수 있다브라우저(클라이언트)의 메모리 상에 많은 데이터를 갖게 되므로, 클라이언트의 부담이 늘어난다
컴포넌트 외부에서 처리클라이언트가 필터링 구현을 생각하지 않아도 된다빈번한 HTTP 요청이 일어나게 되며, 서버가 필터링을 처리하므로 서버가 부담을 가져간다

AJAX 요청보내기

  • fetch API를 써서, 서버에 요청한다면,
    • 명언을 제공하는 API의 엔드포인트가 http://서버주소/proverbs 라고 가정
useEffect(() => {
  fetch(`http://서버주소/proverbs?q=${filter}`)
    .then(resp => resp.json())
    .then(result => {
      setProverbs(result);
    });
}, [filter]);

AJAX 요청이 매우 느릴 경우?

네트워크 요청이 항상 즉각적인 응답을 가져다주는 것은 아닙니다. 외부 API 접속이 느릴 경우를 고려하여, 로딩 화면(loading indicator)의 구현은 필수적임

로딩화면 구현

여기에도 상태 처리가 필요함

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

// 생략, 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]);

퀴즈

  • React에서 Ajax 요청을 보낼 때는 Effect Hook을 사용하는 것이 좋다.(o)
    • 웹 앱을 구성할 때, 서버로의 네트워크 요청을 보내야 되는 경우가 있습니다. React에서는 이러한 Ajax 요청을 처리할 때, Side Effect를 최소화하기 위해서 Effect Hook을 사용합니다. 만약 훅을 사용하지 않고 네트워크 요청을 하면 그 동안에 페이지가 멈추거나 깜빡일 수 있습니다.

실시간세션

  • 사이드 이펙트는 유즈이펙트에 넣어줘야한다.(외우세요)

  • 유즈이펙트 첫번재인자함수에 = 사이드이펙트넣어줘야한다

  • ajax요청 = 네트워크요청 = 사이드이펙트임 = >유즈이펙트안에넣어라

  • 리턴해서 프로미스 객체를 생성, getFlight는 프로미스 객체가 된대
    리졸브안에 필털드라는 변수를

  • fillterby ={] 디폴트값을 정해주는것임

unction func1(filteredBy = {}) {
if (filteredBy.num) return filteredBy.num;
if (filteredBy.str) return filteredBy.str;
}

let obj1 = {};
func1(obj1); // undefined

let obj2 = {num : 1};
func1(obj2); // 1

let obj3 = {str:'문자'}
func1(obj3); // '문자'

  • 2-3, 2-4 그냥 삭제하는거
  • 2-5 로딩화면

  • 종합퀴즈 3번 a 외부에서 내부에 영향을 미치는 것도 비순수
    c 객체를 매개변수로 받아와서 수정만 안하면 순수함
    클로저는 무조건 비순수함수래...

profile
중요한건 꺾이지 않는 마음이 맞는 것 같습니다

0개의 댓글