Frontend/React

React 리액트 - useEffect() (함수형 컴포넌트)

Ayel 2025. 12. 18. 19:16

 

 

참조 투명성

 

 

참조 투명성

 

- 입력받은 값을 그대로 출력하는 함수를 참조 투명하다고 하며, 순수 함수(Pure Function)라고 한다

- 입력받았을 때 다른 값으로 출력하거나 외부 함수에 영향을 끼치는 함수를 참조 불투명하다고 한다

-> 순수함수가 아닌 함수에서 Side-Effect가 발생한다

 

- 리액트는 모든 함수를 순수 함수로 유지할 것을 권장하고 있음

-> 컴포넌트의 재사용성이 좋아지고 오류 검사 및 테스트도 좋아진다

 

 

Side-Effect

 

: 함수 내 특정 동작이 함수 외부에 영향을 끼쳐 프로그램의 동작을 이해하기 어렵게 만드는 행위

a) setTimeOut, setInterval

b) 서버와의 통신(fetct, axios)

c) 그 외 비동기 함수

 

 

useEffect()

 

- Side-Effect를 따로 관리하여 컴포넌트가 최대한 순수 함수를 유지할 수 있도록 도와주는 함수
- useEffect()는 랜더링이 모두 끝난 다음에 실행된다

 


useEffect() 가용 범위


- 랜더링 될 때 마다 실행
-> useEffect(() ⇒ {})

-> useEffect를 콜백함수로 사용 => {} 이 부분에 발생할 사이드이펙트를 넣어라


- 최초 랜더링(마운트)할 때만 실행
-> useEffect(() ⇒ {} , []) // 빈 배열로 들어왔을 때에는 딱 한번만 실행
-> 즉 비워 놓으면 최초 한번만 아예 전달한게 없으니 평생 리랜더링 될 수 없다.
-> 즉 === componentDidMount()와 동일


- 특정 컴포넌트 변경 시 실행
-> useEffect(() ⇒ {} , [state])

 

 

SideEffectMount

 

 

JSONPlaceholder를 활용한 예제 실습

https://jsonplaceholder.typicode.com/users

 

 

JSONPlaceholder

- 프론트엔드에서 서버 없이도 JSON 데이터를 받아오는 연습을 할 수 있도록 만들어진 무료 가짜 API

- 리액트나 다른 프론트엔드/백엔드 개발에서 연습용, 테스트용으로 사용

 

 

jasonplaceholder에서 id값을 가져와 출력
버튼을 누를 때마다 id값을 하나씩 추가해 다음 id값을 출력한다

 

 

import React, { useEffect, useState } from 'react';

const SideEffectMount = () => {

  const [user, setUser] = useState([])
  const [id, setId] = useState(1)
  
  const getUsers = async () => {
    const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`)
    const datas = await response.json()
    return datas
  }
  
  // 마운트가 될 때 딱 한 번만 실행
  useEffect(() => {
    getUsers().then(setUser)
  }, [id]) // 의존성배열

  const handleIdOnClick = () => {
    setId(id +1)
  }

  console.log(id)
  console.log(user)

  return (
    <div>
      <button onClick={handleIdOnClick}>아이디 +1</button>
      {user.name}
    </div>
  );
};

export default SideEffectMount;

 

 

- useEffect를 사용해 마운트 될 때 한 번만 실행

 

 

<결과화면>

 

 

SideEffectTask

 

 

jsonplaceholder에서 데이터를 요청해
화면에 완료하지 못한 할일 목록 중 제목만 li 태그로 출력하기
https://jsonplaceholder.typicode.com/todos/

 

 

 

 

-> 테스트 데이터에서 completed 상태가 false인 항목의 title을 출력해야 한다

 

 

import React, { useState, useEffect } from 'react';

const SideEffectTask = () => {

  const [completed, setCompleted] = useState([]);

  const getTodos = async () => {
    const response = await fetch(`https://jsonplaceholder.typicode.com/todos`);
    const datas = await response.json();
    
    // 완료하지 못한 항목
    return datas.filter(todo => !todo.completed);
  };

  useEffect(() => {
    getTodos().then(setCompleted);
  }, []);

  return (
    <div>
      <h2>UNCOMPLETED [{completed.length}]</h2>

      <ul>
        {completed.map(todo => (
          <li key={todo.id}>{todo.title}</li>
        ))}
      </ul>
    </div>
  );
};

export default SideEffectTask;

 

 

<결과화면>

 

 

import React, { useState, useEffect } from 'react';

const SideEffectTask2 = () => {

  const [todos, setTodos] = useState([])

  // [공식!]
  useEffect(() => {
    const getTodos = async () => {
      const response = await fetch("https://jsonplaceholder.typicode.com/todos")
      const datas = await response.json()
      setTodos(datas)
    }

    getTodos()
  }, [])
  console.log(todos)
  
  const todoList = todos.filter(({completed}) => !completed).map(({title}, i) => (
    <li key={i}>{title}</li>
  ))
  // 조건식은 가급적 하단 리스트에 걸어주는 것이 좋다
  // 그러면 위의 useEffect()를 다른 곳에 재사용할 수 있다

  return (
    <ul>
      {todoList}
    </ul>
  );
};

export default SideEffectTask2;

 

 

useEffect()는 데이터 요청만 담당하고

조건 필터링은 todoList로 jsx 랜더링 단계에서 처리

-> 같은 todos로 여러 조건의 UI 구성이 가능

=> 재사용성이 높다

 

 

SideEffectTask - 페이지 조회

 

 

다음 페이지를 누르면 다음 10개를 조회
이전 페이지를 누르면 이전 10개를 조회

 

 

import React, { useState, useEffect } from 'react';

const SideEffectTask3 = () => {

const [todos, setTodos] = useState([]);
  const [page, setPage] = useState(0);
  const limit = 10;

  useEffect(() => {
    const getTodos = async () => {
      const response = await fetch('https://jsonplaceholder.typicode.com/todos');
      const datas = await response.json();
      setTodos(datas);
    };

    getTodos();
  }, []);

  const start = page * limit;
  const end = start + limit;
  const currentTodos = todos.slice(start, end);

  const totalPages = Math.ceil(todos.length / limit);

  const handlePrev = () => {
    if (page > 0) setPage(prev => prev - 1);
  };

  const handleNext = () => {
    if (page < totalPages - 1) setPage(prev => prev + 1);
  };

  return (
    <div>
      <h2>TO DO LIST</h2>
      <div>
        <button onClick={handlePrev} disabled={page === 0}>
          PREV
        </button>
        <span>
          [{page + 1}/{totalPages}]
        </span>
        <button onClick={handleNext} disabled={page >= totalPages - 1}>
          NEXT
        </button>
      </div>

      <ul>
        {currentTodos.map(({ id, title, completed }) => (
          <li key={id}>
            [{completed ? 'Completed!!!!' : 'Uncompleted'}] {title}
          </li>
        ))}
      </ul>
    </div>
  );

};

export default SideEffectTask3;

 

 

<결과화면>

 

 

 

import React, { useState, useEffect } from 'react';

const SideEffectTask4 = () => {

  const [todos, setTodos] = useState([])
  const [cursor, setCursor] = useState(0)

  // [공식!]
  useEffect(() => {
    const getTodos = async () => {
      const response = await fetch(`https://jsonplaceholder.typicode.com/todos?_start=${cursor*10}&_limit=10`)
      const datas = await response.json()
      setTodos(datas)
    }

    getTodos()
  }, [cursor])
  console.log(todos)
  
  const todoList = todos.filter(({completed}) => !completed).map(({title}, i) => (
    <li key={i}>{title}</li>
  ))

  const handleCursorIncreaseOnClick = () => {
    setCursor(cursor +1)
  }

  const handleCursorDecreaseOnClick = () => {
    setCursor(cursor -1)
  }


  return (
    <ul>
      { cursor <=0 ? (<></>) : (
        <button onClick={handleCursorDecreaseOnClick}>이전 페이지</button>
        )}
      <button onClick={handleCursorIncreaseOnClick}>다음 페이지</button>
      {todoList}
    </ul>
  );
};

export default SideEffectTask4;

 

 

- 페이지마다 서버에서 데이터를 가져옴

- 페이지 이동마다 네트워크 요청

- 실무에 더 적합한 방식

 

 

<결과화면>

 

 

- 가장 첫 페이지에서는 이전페이지 버튼 비활성화