React 클라이언트 Ajax 요청

0

REACT

목록 보기
4/12
post-thumbnail

혹시나 잘못된 개념 전달이 있다면 댓글 부탁드립니다. 저의 성장의 도움이 됩니다

오늘의 Checkpoint

React 데이터 흐름

  • 상향식으로 제작
    : 컴포넌트를 먼저 만들고, 페이지에 맞게 컴포넌트를 조립
    -> 확장성, 테스트 용이

  • 단방향 데이터 흐름 One-way data flow


컴포넌트간의 상호작용, 데이터의 흐름 등을 고려하여 데이터 선언 위치를 설정

  1. 디자인을 전달 받으면 가장 먼저 컴포넌트 계층 구조를 분석
    : 단일 책임 원칙에 따라 하나의 기능을 가진 컴포넌트로 구분

  2. 데이터의 전달 위치 설정
    : 데이터의 흐름(하향식 top-down)을 고려하여 부모 컴포넌트에서 Props 방식으로 데이터 전달

  3. state 를 비롯한 필요한 데이터 정의 및 구분
    -> state는 최소화하는 것이 좋음

    [X] 부모 컴포넌트에서 Props로 전달되는가?
    [X] 시간이 지나도 변하지 않는가?
    [X] 컴포넌트 안의 다른 state나 props로 계산 가능한가?
    -> state가 아닌 일반 데이터

  4. state의 위치 결정
    : 하나의 state가 여러개의 컴포넌트에 영향을 끼칠 경우, 공통되는 부모 컴포넌트에서 선언하고 영향을 끼치지 않으면 사용하는 컴포넌트에서 선언
    ex. 작성중인 상태를 나타내는 state는 새로운 글을 작성하는 컴포넌트에서 정의하여 사용. 반면 게시글 목록을 표현하는 state는 새글을 추가하는 컴포넌트의 영향을 받으므로 게시글 목록 컴포넌트보다 공통적인 부모 컴포넌트에서 state를 정의하여 사용



State 끌어올리기

Lifting State Up
단방향 데이터 흐름의 원칙을 유지하며 하위 컴포넌트로 인해 부모 컴포넌트의 상태가 변하는 상황에 적용하는 방법
-> props로 state 대신 '상태변경함수 Handler' 를 전달
: 하위 컴포넌트에서 전달받은 상태변경함수를 실행하면 state 변경되며 단반향 데이터 흐름 원칙도 유지

import React, { useState } from "react";

export default function ParentComponent() {
  const [value, setValue] = useState(""); 
    
  const handleChangeValue = () => {
    setValue("010-1234-5678"); // setValue는 상태 변경 함수
  };

  return (
    <div>
      <div>{value}</div>
      <ChildComponent handleBtnClick={handleChangeValue} /> 
      {/* props로 콜백 형태로 상태 변경함수 전달*/}
    </div>
  );
}

function ChildComponent({handleBtnClick}) {
  const handleClick = () => {
    handleBtnClick();
  };

  return <button onClick={handleClick}>전화번호 뭐에요?</button>;
  		{/* 버튼 누르면 부모 컴포넌트의 value값 재설정 + 단방향 데이터 흐름 유지 */}
}

트위터 예제

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

const currentUser = "조OO";

// Twittler component
export default function Twittler() {
  const [tweets, setTweets] = useState([]);

  const addNewTweet = (newTweet) => {
    setTweets([...tweets, newTweet]);
  };

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


// NewTweetForm component
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);
  };

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


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

cf. 하위 컴포넌트는 props의 데이터 형태/타입만 알 수 있음



Effet Hook

Side Effect

함수 내의 구현이 외부에 영향을 끼쳐 예상치 못한 결과가 발생한 것
-> 순수 함수의 출력값에 영향을 끼치는 것
-> 컴포넌트의 순수함수적인 특징에 위배

ex. React에서는 fetch로 정보를 가져오거나(네트워크 요청), 타이머API 사용, 이벤트를 통해 DOM을 직접 조작할 때 발생


순수함수

Pure function
외부의 변화(Side effect) 없이, 동일한 입력값에 의한 출력값이 같은 예측가능한 함수
-> immutable
-> 단방향 데이터 흐름 유지

function upper(str) {
  return str.toUpperCase(); 
  // toUpperCase는 immutable하므로 외부의 값을 변형하지 않음
}

const arg= 'hello';
upper(arg); // 'HELLO'
arg; // 'hello'

ex. Math.random() 이 함수내부에 있는경우, 같은 입력에도 다른 결과로 출력되므로 순수함수 X
ex. 네트워트로 요청을 보낼 경우에도 POST, PUT 등에 의해 외부의 값이 변경되므로 순수함수X


React와 순수함수

// React의 props로 전달하여 단방향 데이터 흐름 유지(순수함수)
function SingleTweet({ writer, body, createdAt }) {
  return <div>
    <div>{writer}</div>
    <div>{createdAt}</div>
    <div>{body}</div>
  </div>
}

Ajax 요청, LocalStorage등은 side effect로 취급하기 때문에, React에서는 Effect Hook을 통해 side effect를 핸들링


useEffect

컴포넌트에서 side effect를 다룰 때 사용하는 함수
-> side effect가 발생하는 요인들은 useEffect() 내부에 위치
-> Lifting State Up을 위해 전달받은 상태변경함수 호출 위치

useEffect(func, dependencyArray)
// func : side effect를 핸들링하는 함수
// dependencyArray : useEffect가 실행되는 조건으로 배열 형태
  • 실행 조건 3가지

    • state가 변경되어 렌더링
    • 새 props가 전달되어 렌더링
    • 컴포넌트 생성 후 처음에 렌더링
      -> 렌더링 될 때
  • Hook 사용시 주의사항

    • React 함수 속 최상위에서 Hook 적용
      -> 반복문, 조건문, 중첩함수에서 호출 X
    • React 함수 내부 O, Javascript 함수 내부 X

Dependency Array

: useEffect가 실행되는 조건을 배열로 전달
-> 배열의 요소(값)에 해당하는 부분이 변경되어 렌더링 될 때만 실행

  • 없음 -> useEffect 실행 조건 3가지
  • [] -> 컴포넌트 생성 될 때 1회 실행
    ex. 외부 API로 리소스를 받아오고 후에 API 호출이 없을 경우
  • ['A', 'B']
    -> 'A', 'B' 가 변경되어 렌더링 될때 실행
// getProverbs은 배열 형태

const [proverbs, setProverbs] = useState([]);
const [filter, setFilter] = useState(""); // 사용자입력값 의미
const [count, setCount] = useState(0); // 버튼 누를때 +1 되는 값

useEffect(() => {
	console.log("언제 effect 함수가 불릴까요?");
    const result = getProverbs(filter);
    setProverbs(result);
  }, [...filter, count]);

컴포넌트에서 Ajax 요청

컴포넌트 내부에서 필터링외부에서 필터링
서버 등에서 전체 데이터를 받아온 후,
클라이언트에서 필터링 적용
조건과 함께 서버로 요청하면 필터링 된 값을 받아
클라이언트에서는 전체를 렌더링
장점HTTP 요청 회수 감소클라이언트에서 작업이 줄어듬
단점클라이언트에서 불필요한 데이터까지 저장잦은 HTTP 요청 + 서버에 부담이 생김

ex. 컴포넌트 내부에서 필터링

proverbs.filter(/*생략*/).map(/*생략*/)

ex. 외부에서 필터링

// 비교용 코드만 추출
// proverbs는 state값
useEffect(() => {
    console.log("언제 effect 함수가 불릴까요?");
    const result = getProverbs(filter);
    setProverbs(result);
  }, [filter]);

proverbs.map(/*생략*/)

// 비교용 코드만 추출
// 외부에서 필터링
function getProverbs(filterBy = "") {
  const json = localStorage.getItem("proverbs");
  const proverbs = JSON.parse(json) || [];
  return proverbs.filter((prvb) =>
    prvb.toLowerCase().includes(filterBy.toLowerCase())
  );
}

fetch로 Ajax 요청

Query parameter(필터링 조건)를 uri에 추가하여 get 요청

// 함수 선언부 등 생략
const [filter, setFilter] = useState("");

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

로딩 화면 loading indicator

  1. 로딩 상태를 표현하는 state 선언
  2. state에 따라 조건부 렌더링
// 함수 선언부 등 생략
// LoadingIndicator는 로딩 화면 컴포넌트
const [isLoading, setIsLoading] = useState(true);

useEffect(() => {
  setIsLoading(true); //<----- state 변경
  fetch(`http://서버주소/proverbs?q=${filter}`)
    .then(resp => resp.json())
    .then(result => {
      setProverbs(result);
      setIsLoading(false); //<----- then() 내부에서 state 변경
    });
}, [filter]);

return {isLoading ? <LoadingIndicator /> : <div>로딩 완료 화면</div>}



오늘의 나

느낀점
오늘 공식문서를 제외하면 어렵지 않았어서 얕봤다가 오류때문에 한참 헤맸다. 아무리봐도 체크된 곳에선 오류가 없는데 기존에 알던 부분이 잘못되었나 엄청 찾아보다가 호출할 때 전달인자가 누락되어서 오류가 발생했었다. 선언부분에 오류가 계속 발생해서 선언한 장소만 보다가 호출한 곳에서 문제였던 거다. 앞으로는 오류표시 부분 외에도 관련된 영역도 한번씩 체크해줘야겠다.
그런데 오늘 이것 외에도 공식문서 리엑트 부분이 너무 어려웠다. Hook 소개? 처음부터 안보고 중간을 보니까 더 막막했던 것 같다. 주말에 여유롭게 쭉 보고 싶은데 내용정리도 아직 한참 밀려있다... 욕심부리지 말고 아는데까지만 소화시켜보자

개선점 및 리마인드

  • 오류 표시 외에 관련 된 변수 부분도 체크하기

0개의 댓글