함수 내에서 어떤 구현이 함수 외부에 영향을 끼치는 경우 해당 함수는 Side Effect가 있다고 이야기 합니다.
즉 Side Effect라는 말을 할 때는 "의도하지 않은 결과"를 의미합니다. 의도하지 않게 외부변수를 참조하거나 외부변수를 변경하는 모든 종류의 코드를 의미합니다.
다음은 전역 변수 foo를 bar라는 함수가 수정하는 예제입니다. Side Effect의 예시로 보면 될 것 같습니다.
let foo = 'hello';
function bar() {
foo = 'world';
}
bar(); // bar는 Side Effect를 발생시킵니다!
순수 함수란, 오직 함수의 입력만이 함수의 결과에 영향을 주는 함수를 의미합니다. 함수의 입력이 아닌 다른 값이 함수의 결과에 영향을 미치는 경우, 순수 함수라고 부를 수 없습니다. 또한 순수 함수는, 입력으로 전달된 값을 수정하지 않습니다.
function upper(str) {
return str.toUpperCase(); // toUpperCase 메소드는 원본을 수정하지 않습니다 (Immutable)
}
upper('hello') // 'HELLO'
순수 함수에는 네트워크 요청과 같은 Side Effect가 없습니다.
순수 함수의 특징 중 하나는, 어떠한 전달 인자가 주어질 경우, 항상 똑같은 값이 리턴됨을 보장합니다. 그래서 예측 가능한 함수이기도 합니다.
그렇다면 순수함수는 왜 공부해야할까요?? 사이드 이펙트를 줄이고 모듈화 수준을 높이는 함수형 프로그래밍에서 순수함수는 평가 시점이 무관하다는 특징 때문에 효율적인 로직을 구성할 수 있습니다.
그래서 함수형 프로그래밍을 위해서 순수함수의 개념을 배워야 합니다
Math.random()은 아무런 인자를 입력하지 않았음에도 함수를 호출할 때마다 결과가 매번 달라집니다.
그래서 순수함수가 아닙니다. 만약 순수함수가 되려면 아무런 인자를 입력하지 않아도 출력 결과는 매번 똑같아야 하는거죠.
Math.random()의 결과는 다음과 같습니다.
Math.random();
0.07967273312798584
Math.random();
0.08614769407005318
Math.random();
0.9263392609067878
Math.random();
0.9795463094099406
Math.random();
0.7978819439429943
Math.random();
0.5609384445047261
왜냐하면 fetch api는 실행할 때마다 네트워크의 상황과 서버상태에 따라 응답코드가 달라지기 때문에 예측불가능합니다.
예를들어 fetch api를 이용해서 서버에 ajax를 요청할 때마다 응답 상태코드가 200, 404, 302 등 달라질 수도 있고 리스폰스에 대한 결과도 계속 달라질수도 있기 때문에 그렇습니다.
우리가 앞서 배운 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 또는 타이머와 같은 React와 상관없는 API를 사용하는 경우가 발생할 수 있습니다.
이는 React의 입장에서는 전부 Side Effect입니다. React는 Side Effect를 다루기 위한 Hook인 Effect Hook을 제공합니다.
useEffect는 컴포넌트 내에서 Side effect를 실행할 수 있게 하는 Hook입니다.
이 컴포넌트에서 실행하는 Side effect는 브라우저 API를 이용하여, 타이틀을 변경하는 것입니다. 다음 코드를 확인해 보세요.
import { useEffect, useState } from "react";
import "./styles.css";
export default function App() {
const proverbs = [
"좌절감으로 배움을 늦추지 마라",
"Stay hungry, Stay foolish",
"Memento Mori",
"Carpe diem",
"배움에는 끝이 없다"
];
const [idx, setIdx] = useState(0);
const handleClick = () => {
setIdx(idx === proverbs.length - 1 ? 0 : idx + 1);
};
return (
<div className="App">
<button onClick={handleClick}>명언 제조</button>
<Proverb saying={proverbs[idx]} />
</div>
);
}
function Proverb({ saying }) {
useEffect(() => {
document.title = saying;
});
return (
<div>
<h3>오늘의 명언</h3>
<div>{saying}</div>
</div>
);
}
useEffect의 첫 번째 인자는 함수입니다. 해당 함수 내에서 side effect를 실행하면 됩니다. 이 함수는 다음과 같은 조건에서 실행됩니다.
이와 같이 매번 새롭게 컴포넌트가 렌더링 될 때 Effect Hook이 실행됩니다.
useEffect의 두 번째 인자는 배열입니다. 이 배열은 조건을 담고 있습니다.
여기서 조건은 boolean 형태의 표현식이 아닌, 어떤 값의 변경이 일어날 때를 의미합니다.
따라서, 해당 배열엔 어떤 값의 목록이 들어갑니다. 이 배열을 특별히 종속성 배열이라고 부릅니다.
만일 종속성 목록에 아무런 종속성도 없다면 어떤 일이 발생할까요? 달리 말해, 두 번째 배열을 빈 배열[]로 둘 경우에는 무슨 일이 발생할까요? 두 번째 인자를 아예 안 넘기는 것과 어떻게 다를까요?
빈 배열 넣기
useEffect(함수, [])
(2번) 기본 형태의 useEffect는 컴포넌트가 처음 생성되거나, props가 업데이트되거나, 상태(state)가 업데이트될 때 effect 함수가 실행됨을 앞서 배웠습니다.
반면에 (1번) 빈 배열을 useEffect의 두 번째 인자로 사용하면, 이때에는 컴포넌트가 처음 생성될 때만 effect 함수가 실행됩니다. 이것이 언제 필요할까요? 대표적으로 처음 단 한 번, 외부 API를 통해 리소스를 받아오고 더 이상 API 호출이 필요하지 않을 때에 사용할 수 있습니다.
네트워크 요청은 Side Effect 를 발생시키는 대표적인 이벤트다. AJAX 로 데이터를 요청하고 useEffext를 사용하여 그 데이터 내에서 필터링을 거쳐 내가 원하는 데이터만을 가져오고자 할 때의 방법은 두 가지가 있다. 첫 번째는 컴포넌트 내부에서 필터링을 하는 것이고, 두 번째는 컴포넌트 외부에서 필터링을 하는 것이다.
서버에 요청으로 전체 데이터를 다 불러오고, 목록을 검색어로 필터링을 한다. 장점으로는 HTTP 요청의 빈도를 줄일 수 있다는 점이다. 단점으로는 클라이언트인 브라우저가 메모리 상으로 필터링을 하는 것까지 추가하게 되어 클라이언트에서 짊어야 할 부담감이 커진다는 것이다.
대부분 서버에서 알아서 필터링을 하고 필터링된 목록을 들여와달라고 요청할 수 있다. 장점으로는 클라이언트가 필터링을 생각하지 않아도 된다는 점이다. 단점으로는 빈번히 HTTP 요청이 일어나고 서버가 필터링을 하면서 서버에 부담이 갈 수 있다는 점이다.