참조 투명성
참조 투명성
- 입력받은 값을 그대로 출력하는 함수를 참조 투명하다고 하며, 순수 함수(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;
- 페이지마다 서버에서 데이터를 가져옴
- 페이지 이동마다 네트워크 요청
- 실무에 더 적합한 방식
<결과화면>


- 가장 첫 페이지에서는 이전페이지 버튼 비활성화
'Frontend > React' 카테고리의 다른 글
| React 리액트 - 리액트 라이프사이클 (클래스형 컴포넌트) (1) | 2025.12.18 |
|---|---|
| React 리액트 - useRef() vs useState() (0) | 2025.12.11 |
| React 리액트 - 레퍼런스(Ref) (0) | 2025.12.11 |
| React 리액트 - 맵(Map) (0) | 2025.12.10 |
| React 리액트 - 상태(state) (0) | 2025.12.10 |