함수 내에서 어떤 구현이 함수 외부에 영향을 끼치는 경우 해당 함수는 Side Effect
가 있다고 이야기한다.
React에서는 컴포넌트 내에서 fetch
를 사용해 API
정보를 가져오거나 이벤트를 활용해 DOM
직접 조작할 때 Side Effect
가 발생했다고 말한다.
다음은, 전역 변수 foo
를 bar
라는 함수가 수정하는 예제이다.
let foo = 'hello';
function bar() {
foo = 'world';
}
bar(); // bar는 Side Effect를 발생시킨다!
순수 함수란, 오직 함수의 입력만이 함수의 결과에 영향을 주는 함수를 의미한다.
함수의 입력이 아닌 다른 값이 함수의 결과에 영향을 미치는 경우, 순수 함수라고 부를 수 없다.
또한 순수 함수는, 입력으로 전달된 값을 수정하지 않는다.
function upper(str) {
return str.toUpperCase(); // toUpperCase 메소드는 원본을 수정하지 않는다 (Immutable)
}
upper('hello') // 'HELLO'
Side Effect
가 없다.props
가 입력으로, JSX Element
가 출력으로 나간다. 여기에는 그 어떤 Side Effect
도 없으며, 순수 함수로 작동한다.function SingleTweet({ writer, body, createdAt }) {
return <div>
<div>{writer}</div>
<div>{createdAt}</div>
<div>{body}</div>
</div>
}
AJAX
요청이 필요하거나, LocalStorage 또는 타이머와 같은 React와 상관없는 API를 사용하는 경우가 발생할 수 있다.Side Effect
이다.Side Effect
를 다루기 위한 Hook
인 Effect Hook
을 제공한다.useEffect
는 컴포넌트 내에서 Side effect
를 실행할 수 있게 하는 Hook이다.Side effect
는 브라우저 API
를 이용하여, 타이틀을 변경하는 것이다.useEffect(함수)
useEffect
의 첫 번째 인자는 함수이다. 해당 함수 내에서 side effect
를 실행하면 된다.props
가 전달되며 렌더링state
)가 바뀌며 렌더링Effect Hook
이 실행된다.useEffect
의 두 번째 인자는 배열이다.useEffect
가 실행된다.filter
가 변할 때에만, effect
함수가 실행된다.effect
함수는 실행되지 않는다.
- 명언 목록 (
proverbs
)- 필터링할 문자열 (
effect
)- 카운트 (
count
)
filter
만 존재하고, count
는 존재하지 않기 때문이다.useEffect(() => {
console.log("언제 effect 함수가 불릴까요?");
const result = getProverbs(filter);
setProverbs(result);
}, [filter]);
useEffect(함수, [종속성1, 종속성2, ...])
useEffect
의 두 번째 인자는 종속성 배열이다.[]
로 둘 경우) 어떤 일이 발생할까?아무것도 넣지 않기 (기본 형태)
useEffect(함수)
useEffect
는 컴포넌트가 처음 생성되거나, props
가 업데이트되거나, 상태(state
)가 업데이트될 때 effect
함수가 실행된다.빈 배열 넣기
useEffect(함수, [])
빈 배열을 useEffect
의 두 번째 인자로 사용하면, 이때에는 컴포넌트가 처음 생성될 때만 effect
함수가 실행된다.
이 방법은 대표적으로 처음 단 한 번, 외부 API
를 통해 리소스를 받아오고 더 이상 API 호출이 필요하지 않을 때에 사용할 수 있다.
- 컴포넌트 내에서 필터링:
전체 목록 데이터를 불러오고, 목록을 검색어로filter
하는 방법- 컴포넌트 외부에서 필터링:
컴포넌트 외부로 API 요청을 할 때, 필터링 한 결과를 받아오는 방법 (보통, 서버에 매번 검색어와 함께 요청하는 경우가 이에 해당한다)
처음 단 한 번, 외부 API로부터 명언 목록을 받아오고, filter
함수를 이용한다.
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>;
}
검색어가 바뀔 때마다, 외부 API를 호출한다.
import { useEffect, useState } from "react";
import "./styles.css";
import { getProverbs } from "./storageUtil";
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>;
}
장점 | 단점 | |
---|---|---|
컴포넌트 내부에서 처리 | HTTP 요청의 빈도를 줄일 수 있다. | 브라우저(클라이언트)의 메모리 상에 많은 데이터를 갖게 되므로, 클라이언트의 부담이 늘어난다. |
컴포넌트 외부에서 처리 | 클라이언트가 필터링 구현을 생각하지 않아도 된다. | 빈번한 HTTP 요청이 일어나게 되며, 서버가 필터링을 처리하므로 서버가 부담을 가져간다. |
fetch API
를 써서, 서버에 요청한다면 코드는 어떻게 될까?API
의 엔드포인트가 http://서버주소/proverbs
라고 가정해보자.useEffect(() => {
fetch(`http://서버주소/proverbs?q=${filter}`)
.then(resp => resp.json())
.then(result => {
setProverbs(result);
});
}, [filter]);
외부 API 접속이 느릴 경우를 고려하여, 로딩 화면(loading indicator)의 구현은 필수적이다.
기본적으로, Loading indicator의 구현은 어떻게 처리할 수 있을까?
여기에도 상태 처리가 필요하다.
const [isLoading, setIsLoading] = useState(true);
// 생략, LoadingIndicator 컴포넌트는 별도로 구현했음을 가정합니다
return {isLoading ? <LoadingIndicator /> : <div>로딩 완료 화면</div>}
fetch
요청의 전후로 setIsLoading
을 설정해 주어 보다 나은 UX를 구현할 수 있다.
useEffect(() => {
setIsLoading(true);
fetch(`http://서버주소/proverbs?q=${filter}`)
.then(resp => resp.json())
.then(result => {
setProverbs(result);
setIsLoading(false);
});
}, [filter]);