[React] 데이터 흐름, Effect Hook

전예훈·2023년 4월 3일
0

React 데이터 흐름

  • React 개발 방식은 페이지 단위가 아닌, 컴포넌트 단위로 시작 ( 제일 낮은것은 element)
  • React 데이터는 하향식으로 props를 전달한다.

Props vs State

React에는 두 가지 데이터 모델인 props 와 state 가 존재한다.

한마디로 정의 하면

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

state : 상호작용을 위한것으로 시간이 지남에 따라 데이터가 바뀌는 것에 사용

State 끌어올리기

단방향 데이터 흐름 = 하향식

  • 컴포넌트는 컴포넌트 바깥에서 props를 이용해 데이터를 마치 전달인자 혹은 속성 처럼 전달 받을 수 있고 부모 컴포넌트는 자신의 state 를 자식 컴포넌트에 props로 전달 가능하다.

역방향 데이터 흐름 = State 끌어올리기

  • 그림처럼 단방향 데이터 흐름 원칙에 따라 하위 컴포넌트는 상위 컴포넌트로부터 전달받은 데이터 혹은 형태 타입만 알 수 있고, 데이터가 state로 왔는지, 하드코딩으로 입력한 내용인지는 알지 못한다.

  • 그래서 하위컴포넌트에서의 어떤 이벤트로 인해 상위 컴포넌트의 상태가 바뀌는 것은 "역방향 데이터 흐름" 과 같이 이상하게 들릴 수 있다.

이때 필요한 것인 상태 끌어올리기 (Lifting State Up)이다.

예제1. state 끌어올리기 하는 방법

  • 상태 갱신 함수는 setValue, 상태를 변경하는 함수는 handleChangeValue

  • props로 handleChangeValue를 자식 컴포넌트에 전달할 것임

  • props의 이름은 handleButtonClick으로 짓기

  • "얘"를 자식 컴포넌트 {인자}에 넣고
    자식 컴포넌트 내에 “얘”를 실행시킬 함수블록 내에 넣어줌

import React, { useState } from "react";

//부모 컴포넌트
export default function ParentComponent() {
  const [value, setValue] = useState("날 바꿔줘!");

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

  return (
    <div>
      <div>값은 {value} 입니다</div>
      <ChildComponent handleButtonClick={handleChangeValue} />
// 1번
// 상태를 변경하려는 함수 자체를 하위컴포넌트로 전달해야 한다고 했으므로
// 상태를 변경해야 하는 함수는 handleChangeValue 이고
// 전달은 props를 이용한다
// 하위 컴포넌트가 버튼 클릭 이벤트에 따라 상태를 변경하니
// 이름은 handleButtonClick이라고 지어줬다
    </div>
  );
}

//자식 컴포넌트
function ChildComponent({ handleButtonClick }) {
// 2번 props로 함수를 전달 받아온다
  const handleClick = () => {
    // 이 버튼을 눌러서 부모의 상태를 바꿀 순 없을까?
    handleButtonClick();
// 3번
// ChildComponent는 마치 고차함수가 인자로 받은 함수를 실행하듯,
// props로 전달받은 함수를 컴포넌트 내에서 실행할 수 있게 된다
// "상태 변경 함수"는 버튼이 클릭할 때 실행되기를 원하므로, 해당 부분에 콜백 함수를 실행하면 작동한다
  };

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

Side Efeect


//전역 변수 foo를 bar라는 함수가 수정하는 예제
   let foo = 'hello';

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

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

리액트에서의 Side Effect

  • 컴포넌트 내에서 fetch를 사용해 API를 받아오는 경우
  • 이벤트를 활용해 Dom에서 조작하는 경우

Pure Function (순수 함수)

  • 오직 함수의 입력만이 함수의 결과를 영향을 주는 함수
  • 입력으로 전달된 값을 수정하지 않는다.
  • 순수 함수에는 네트워크 요청과 같은 side effect 가 없다.
function upper(str) {
  return str.toUpperCase(); // toUpperCase 메소드는 원본을 수정하지 않습니다 (Immutable)
}

upper('hello') // 'HELLO'

React의 함수 컴포넌트

  • React의 함수 컴포넌트는 props가 입력으로, JSX Element가 출력으로 나가기 때문에 어떤 Side Effect도 없으며 순수함수로 작동된다.

  • 하지만 보통 React 애플리케이션을 작성할 때에는, AJAX 요청이 필요하거나, LocalStorage 또는 타이머와 같은 React와 상관없는 API를 사용하는 경우가 발생할 수 있습니다. 이는 React의 입장에서는 전부 Side Effect 입니다. React는 Side Effect를 다루기 위한 Hook인 Effect Hook을 제공합니다.

React 에서 Side effect가 발동하는 것은

  • 타이머 사용 (set timeout)
  • 데이터 가져오기 ( fetch API, localStorage)

Effect Hook

Effect Hook 은 언제 실행 될까?

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

⇒ 이와 같이 매번 새롭게 컴포넌트가 렌터링 될 때 Effect Hook이 실행됩니다.

useEffect 3가지 형태

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

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

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

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

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

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

(배열은 =Dependency Array 라고도 불리며 줄여서 Dep 라고도함)

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

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

컴포넌트가 처음 생성될 때만 단 한 번만, effect 함수가 실행됩니다.

Effect Hook을 컴포넌트 내에서 최초 1회만 호출하고 싶을 때 사용
ex) 대표적으로 처음 단 한 번, 외부 API를 통해 리소스를 받아오고 더 이상 API 호출이 필요하지 않을 때에 사용

Hook 을 사용할때 가장 중요한 점!

  • 최상위에서만 Hook을 호출한다.
    반복문, 조건문 혹은 중첩된 함수 내에서 Hook 호출 불가능
  • React 함수 내에서 Hook을 호출한다.
    일반적인 Javascript 함수에서 호출 불가능

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); //로딩 false에서 true 동작
  fetch(`http://서버주소/proverbs?q=${filter}`) 
    .then(resp => resp.json())
    .then(result => {
      setProverbs(result);
      setIsLoading(false); 
    });
}, [filter]);

React에서 Ajax 요청을 보낼 때는 Effect Hook을 사용하는 것이 좋다.

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

profile
캐치테이블 QA

0개의 댓글