오늘의 체크포인트
- Side effect가 어떤 의미인지 알 수 있다.
- React 컴포넌트를 만들 때 side effect로부터 분리해서 생각할 수 있다. (비즈니스 로직과 표현 영역 구분)
- Side effect의 예를 들 수 있다.
- Effect Hook을 이용해 비동기 호출 및 AJAX 요청과 같은 side effect를 React 컴포넌트 내에서 처리할 수 있다.
- Effect Hook에서의 dependency array 사용법을 이해할 수 있다.
- 컴포넌트 내에서 네트워크 요청 시, 로딩 화면과 같이 보다 나은 UI를 만드는 법을 이해할 수 있다.
함수 외부
에 영향을 끼치는 경우 해당 함수는 Side Effect가 있다고 이야기합니다. let count = 0
function greetWithSideEffect(name) { // Input
count = count + 1 // Side Effect!
return `${name}님 안녕하세요!` // Output
}
순수 함수란
, 오직 함수의 입력만이 함수의 결과에 영향을 주는 함수를 의미합니다.
항상 똑같은 값이 리턴됨을 보장
합니다. 그래서 예측 가능한 함수이기도 합니다.function upper(str) {
return str.toUpperCase(); // toUpperCase 메소드는 원본을 수정하지 않습니다 (Immutable)
}
upper('hello') // 'HELLO'
질문
- Math.random()은 순수 함수가 아닙니다. 왜일까요?
A: Math.random은 어떤 결과값이 나올지 예측이 불가능하기 때문에 순수 함수가 아니다.- 어떤 함수가 fetch API를 이용해 AJAX 요청을 한다고 가정해 봅시다. 이 함수는 순수 함수가 아닙니다. 왜일까요?
A:네트워크 상황, 서버상태에 따라 응답코드가 달라지기 때문에 예측이 불가능하다.
우리가 앞서 배운 React의 함수 컴포넌트는,
props가 입력으로, JSX Element가 출력
으로 나갑니다. 여기에는 그 어떤 Side Effect도 없으며, 순수 함수로 작동합니다.
하지만 보통 React 애플리케이션을 작성할 때에는, AJAX 요청이 필요하거나, LocalStorage 또는 타이머와 같은 React와 상관없는 API를 사용하는 경우가 발생할 수 있습니다. 이는 React의 입장에서는 전부 Side Effect
입니다. React는 Side Effect를 다루기 위한 Hook인 Effect Hook을제공합니다.
useEffect
는 컴포넌트 내에서 Side effect를 실행할 수 있게 하는 Hook 입니다.
useEffect의 첫 번째 인자는 함수
입니다. 해당 함수 내에서 side effect를 실행하면 됩니다.
이와 같이 매번 새롭게 컴포넌트가 렌더링
될 때 Effect Hook이 실행됩니다.
Hook을 쓸 때 주의할 점
최상위에서만 Hook을 호출합니다.-> 이 규칙을 따르면 컴포넌트가 렌더링 될 때마다 항상 동일한 순서로 Hook이 호출되는 것이 보장됩니다.
React 함수 내에서 Hook을 호출합니다.->Hook을 일반적인 JavaScript 함수에서 호출하지 마세요
(다음페이지에 쓰기)
useEffect의 두 번째 인자는 배열
입니다.
어떤 값의 변경이 일어날 때를 의미
합니다. 따라서, 해당 배열엔 어떤 값의 목록이 들어갑니다. 이 배열을 특별히 종속성 배열
이라고 부릅니다.예를들어서 count라는 state가 있다고 하면 useEffect의 두번째 인자에 [count]를 작성할시,
count가 변경될때마다 첫번째 인자 함수를 실행시킵니다.
useEffect의 두 번째 인자는 종속성 배열입니다. 배열 내의 종속성1, 또는 종속성2의 값이 변할 때, 첫 번째 인자의 함수가 실행됩니다.
배열 내의 어떤 값이 변할 때에만, (effect가 발생하는) 함수가 실행됩니다.
<컴포넌트내 호출의 차이>
useEffect(() => {
console.log(몇 번 호출될까요?)
})
->A. 컴포넌트가 처음 생성되거나, props가 업데이트 되거나, state가 업데이트 될 때마다 실행됩니다.
useEffect(() => {
console.log(몇 번 호출될까요?)
},[])
B. 컴포넌트가 처음 생성될 때만 함수가 실행됩니다.
->예시: 대표적으로 처음 단 한 번, 외부 API를 통해 리소스를 받아오고 더 이상 API 호출이 필요하지 않을 때에 사용할 수 있습니다.
useEffect(() => {
console.log(몇 번 호출될까요?)
},[dep])
C. dep이 업데이트 될 때마다 실행됩니다.
목록 내 필터링을 구현하기 위해서는 다음과 같은 두 가지 접근이 있을 수 있습니다.
컴포넌트 내에서 필터링: 전체 목록 데이터를 불러오고, 목록을 검색어로 filter 하는 방법
컴포넌트 외부에서 필터링: 컴포넌트 외부로 API 요청을 할 때, 필터링 한 결과를 받아오는 방법 (보통, 서버에 매번 검색어와 함께 요청하는 경우가 이에 해당합니다)
- 컴포넌트 내에서 필터링
처음 단 한 번, 외부 API로부터 명언 목록을 받아오고, filter 함수를 이용합니다.
- 컴포넌트 외부에서 필터링
검색어가 바뀔 때마다, 외부 API를 호출합니다. (앞서 조건부 실행 콘텐츠에서 봤던 예제와 동일합니다)
장점 | 단점 | |
---|---|---|
컴포넌트 내부에서 처리 | HTTP 요청의 빈도를 줄일 수 있다 | 브라우저(클라이언트)의 메모리 상에 많은 데이터를 갖게 되므로, 클라이언트의 부담이 늘어난다 |
컴포넌트 외부에서 처리 | 클라이언트가 필터링 구현을 생각하지 않아도 된다 | 빈번한 HTTP 요청이 일어나게 되며, 서버가 필터링을 처리하므로 서버가 부담을 가져간다 |
AJAX 요청을 보냅시다!
임의로 구현한 storageUtil.js 대신, fetch API를 써서, 서버에 요청한다면 코드는 어떻게 될까요? 명언을 제공하는 API의 엔드포인트가 http://서버주소/proverbs 라고 가정해 봅시다.
useEffect(() => {
fetch(`http://서버주소/proverbs?q=${filter}`)
.then(resp => resp.json())
.then(result => {
setProverbs(result);
});
}, [filter]);
AJAX 요청이 매우 느릴 경우?
모든 네트워크 요청이 항상 즉각적인 응답을 가져다주는 것은 아닙니다. 외부 API 접속이 느릴 경우를 고려하여, 로딩 화면(loading indicator)의 구현은 필수적입니다.
기본적으로, Loading indicator의 구현은 어떻게 처리할 수 있을까요? 여기에도 상태 처리가 필요합니다.
const [isLoading, setIsLoading] = useState(true);
// 생략, LoadingIndicator 컴포넌트는 별도로 구현했음을 가정합니다
return {isLoading ? <LoadingIndicator /> : <div>로딩 완료 화면</div>}
useEffect(() => {
setIsLoading(true);
fetch(`http://서버주소/proverbs?q=${filter}`)
.then(resp => resp.json())
.then(result => {
setProverbs(result);
setIsLoading(false);
});
}, [filter]);
웹 앱을 구성할 때, 서버로의 네트워크 요청을 보내야 되는 경우가 있습니다. React에서는 이러한 Ajax 요청을 처리할 때, Side Effect를 최소화하기 위해서 Effect Hook을 사용합니다. 만약 훅을 사용하지 않고 네트워크 요청을 하면 그 동안에 페이지가 멈추거나 깜빡일 수 있습니다.