[React] State 끌어올리기, Effect Hook, Ajax 요청

jungmin Lee·2023년 9월 12일
0

State 끌어올리기

단방향 데이터 흐름이라는 원칙에 따라, 하위 컴포넌트는 상위 컴포넌트로부터 전달받은 데이터의 형태 혹은 타입이 무엇인지만 알 수 있다. 데이터가 state로부터 왔는지, 하드코딩으로 입력한 내용인지는 알지 못한다.
하위 컴포넌트에서의 어떤 이벤트로 상위 컴포넌트의 상태가 바뀌는 것은 역방향 데이터 흐름처럼 보일 수 있지만 상위 컴포넌트의 상태를 변경하는 함수 자체를 하위 컴포넌트로 전달하고, 이 함수를 하위 컴포넌트가 실행한다. 단방향 데이터 흐름의 원칙에 부합하는 해결 방법이며 상태 끌어올리기이라고 한다.

import React, { useState } from "react";

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

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

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

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

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

Effect Hook

useEffect는 컴포넌트 내에서 Side effect를 실행할 수 있게 하는 Hook이다. useEffect Hook은 컴포넌트가 렌더링될 때마다 실행되며, 특정 상황 또는 종속성이 변경될 때 실행되도록 설정할 수 있다. 컴포넌트의 렌더링 주기와는 독립적으로 side effect를 관리할 수 있다.

Side Effect (부수 효과)
함수 내에서 어떤 구현이 함수 외부에 영향을 끼치는 경우 해당 함수는 Side Effect가 있다고 한다. React에서는 컴포넌트 내에서 fetch를 사용해 API 정보를 가져오거나 이벤트를 활용해 DOM 직접 조작할 때 Side Effect가 발생했다고 한다.

Pure Function (순수 함수)
순수 함수란는 함수의 입력만이 함수의 결과에 영향을 주는 함수이다. 함수의 입력이 아닌 다른 값이 함수의 결과에 영향을 미치는 경우, 순수 함수라고 부를 수 없으며, 입력으로 전달된 값을 수정하지 않는다.


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 또는 타이머와 같은 API를 사용하는 경우가 발생할 수 있다. React의 입장에서는 전부 Side Effect이며, Side Effect를 다루기 위한 Effect Hook을 제공된다.

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

useEffect(함수)

useEffect의 첫 번째 인자는 함수이며, 해당 함수 내에서 side effect를 실행하면 된다.
useEffect의 실행은 컴포넌트 생성 후 처음 화면에 렌더링(표시), 컴포넌트에 새로운 props가 전달되며 렌더링, 컴포넌트에 상태(state)가 바뀌며 렌더링될 때와 같이 매번 새롭게 컴포넌트가 렌더링 될 때 Effect Hook이 실행된다.
useEffect의 두 번째 인자는 배열이며, 어떤 값의 변경이 일어날 때를 의미하는 조건을 담고 있다. 따라서, 해당 배열에는 어떤 값의 목록이 들어가며, 이 배열을 특별히 종속성 배열이라고 부른다.

조건부 effect 발생 (dependency array)

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

배열 내의 종속성1, 또는 종속성2의 값이 변할 때, 첫 번째 인자의 함수가 실행된다.

Hook을 쓸 때 주의할 점

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

빈 배열 넣기, 단 한 번만 실행되는 Effect 함수
빈 배열을 useEffect의 두 번째 인자로 사용하면, 컴포넌트가 처음 생성될 때만 effect 함수가 실행된다. 외부 API를 통해 리소스를 받아오고, API 호출이 필요하지 않을 때에 사용할 수 있다.

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

아무것도 넣지 않기 (기본 형태)
기본 형태의 useEffect는 컴포넌트가 처음 생성되거나, props가 업데이트되거나, 상태(state)가 업데이트될 때 effect 함수가 실행된다.

useEffect(함수)

컴포넌트 내에서의 Ajax 요청

Data Fetching

컴포넌트 내에서 필터링
처음에 한 번만 외부 API로부터 명언 목록을 받아오고, filter 함수를 이용한다.

=> HTTP 요청의 빈도를 줄일 수 있다. 그러나 브라우저(클라이언트)의 메모리 상에 많은 데이터를 갖게 되므로, 클라이언트의 부담이 늘어난다.

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

export default function App() {
  const [proverbs, setProverbs] = useState([]);
  const [filter, setFilter] = useState("");

  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>;
}

<br/ >

컴포넌트 외부에서 필터링
검색어가 바뀔 때마다, 외부 API를 호출한다.

=>클라이언트가 필터링 구현을 생각하지 않아도 된다. 빈번한 HTTP 요청이 일어나게 되며, 서버가 필터링을 처리하므로 서버가 부담을 가져간다.

export default function App() {
  const [proverbs, setProverbs] = useState([]);
  const [filter, setFilter] = useState("");
  const [count, setCount] = useState(0);

  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>;
}

AJAX 요청 보내기

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

컴포넌트가 렌더링될 때 또는 filter가 변경될 때마다 서버로 요청을 보내고 새로운 데이터를 가져와 화면에 표시할 수 있다.

AJAX 요청이 매우 느릴 경우?
모든 네트워크 요청이 바로 응답을 가져다주는 것은 아니다. 외부 API 접속이 느릴 경우를 고려하여, 로딩 화면(loading indicator)의 구현은 필수적이다.

  • loading indicator

  • loading placeholder

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

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

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

0개의 댓글