Today I Learned
- Side Effect
- Effect Hook
함수 내에서 어떤 구현이 함수 외부에 존재하는 값이나 상태에 영향을 끼치는 행위
React에서는 컴포넌트가 화면에 렌더링된 이후에 비동기로 처리되어야 하는 부수적인 효과들을 흔히 Side Effect라고 한다.
React에서 Side Effect의 대표적인 예
let foo = 'hello';
function bar() {
foo = 'world';
}
bar();
console.log(foo) // 'world'
위의 예제에서 전역 변수 foo를 bar라는 함수가 수정했다 (재할당됨). 여기서 함수 bar는 Side Effect를 발생시킨 것이다.
오직 함수의 입력만이 함수의 결과에 영향을 주는 함수
보통 Side Effect를 설명할 때 함께 언급되는 개념이다. 순수함수는 Side Effect가 없는 함수로, 동일한 인수가 들어갈 경우 항상 같은 값을 리턴하며 외부 상태에 영향을 끼치지 않는 함수이다. 그래서 예측 가능한 함수이기도 하다.
function upper(str) {
return str.toUpperCase();
}
upper("hello") // 'HELLO'
// toUpperCase 메서드는 원본 인수를 수정X
// upper 함수는 순수함수이다.
Math.random()
이 순수 함수가 아닌 이유?
: 실행할 때마다 무작위로 0~1 사이의 값을 반환하므로 동일 인수를 전달 했을 때, 항상 같은 값이 리턴됨이 보장되지 않는다.
AJAX 요청
을 하는 함수가 순수 함수가 아닌 이유?
: 네트워크 및 서버 상태에 따라 응답이 달라질 수 있으므로 마찬가지로 동일 인수를 전달 했을 때, 항상 같은 값이 리턴됨이 보장되지 않는다.
리액트에서는 AJAX 요청, Timer API나 LocalStorage API 사용하는 경우, Side Effect가 발생할 수 있다. 이 때, React는 Side Effect를 다루기 위한 Hook인 Effect Hook을 제공한다.
순수함수 참고
useEffect
는 컴포넌트 내에서 Side Effect를 실행할 수 있게 하는 Hook이다.
어떤 값이 변경될 때마다 특정 코드를 실행시키고 싶을 때, 또는 최초 렌더링 시에만 한번 데이터를 받아오고 싶을 때 주로 useEffect
를 사용한다.
// useEffect(함수, [종속성1, 종속성2, ...])
useEffect(() => {}, [])
useEffect
의 첫 번째 인자에는 실행시키고 싶은 함수, 두 번째 인자에는 의존성 배열(dependency array)이 온다.
의존성 배열은 조건을 담고 있는데 여기서 조건은 어떤 값의 변경이 일어날 때를 의미한다. 이 배열 안에 들어간 값이 변경될 때마다 첫 번째 인자의 함수가 실행된다.
import React, { useState, useEffect } from 'react';
const fruits = ['apple', 'strawberry', 'banana', 'peach', 'grape'];
function Example() {
const [count, setCount] = useState(0);
const [value, setValue] = useState('');
useEffect(() => {
// count 상태가 변경될 때마다 타이틀이 변경된다.
document.title = fruits[Math.floor(Math.random() * 5)];
}, []);
return (
<div>
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
/>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
export default Example;
최초 렌더링 시, useEffect 내의 함수가 실행되면서 fruits 배열에서 무작위로 선택된 요소가 상단 탭의 타이틀이 된다. 이후 Click me 버튼을 누르거나 input 창에 타이핑을 해 count 값이나 value 값을 시켜도 useEffect 내의 함수는 다시 실행되지 않는다.
import React, { useState, useEffect } from 'react';
const fruits = ['apple', 'strawberry', 'banana', 'peach', 'grape'];
function Example() {
const [count, setCount] = useState(0);
const [value, setValue] = useState('');
useEffect(() => {
// count 상태가 변경될 때마다 타이틀이 변경된다.
document.title = fruits[Math.floor(Math.random() * 5)];
}, [count]);
return (
<div>
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
/>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
export default Example;
위 코드에서 Click me 버튼을 누르면 count 값이 업데이트 되고, useEffect의 의존성 배열은 count 값이 변경됨을 캐치해 함수를 실행한다. 그래서 count 값이 변경될 때마다 상단 탭의 타이틀이 랜덤으로 변경된다. value 값은 의존성 배열 내에 담지 않았기 때문에 변경되더라도 타이틀이 변경되지 않는다.
useEffect의 두 번째 인자는 사실 필수 값은 아니다. 의존성 배열을 생략해버리면 최초 렌더링 시 한 번, 그리고 이후 리렌더링이 발생할 때마다 useEffect 내의 함수가 실행 된다.
import React, { useState, useEffect } from 'react';
const fruits = ['apple', 'strawberry', 'banana', 'peach', 'grape'];
function Example() {
const [count, setCount] = useState(0);
const [value, setValue] = useState('');
useEffect(() => {
// count, value 상태가 변경될 때마다 타이틀이 변경된다.
document.title = fruits[Math.floor(Math.random() * 5)];
});
return (
<div>
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
/>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
export default Example;
위의 코드는 의존성 배열에 count만 담았던 때와 달리, value 값이 변경될 때도 상단 탭의 타이틀이 변경된다. count 값이 변경될 때, value 값이 변경될 때 둘 다 리렌더링이 발생하기 때문이다.