Hook은 함수 컴포넌트에서 React state와 생명주기 기능(lifecycle features)을 연동(hook into) 할 수 있게 해주는 함수입니다.
-React 공식문서
나중에 state나 prop이 바뀔 때 다시 한 번 VDOM에서 바뀐 내용들을 확인하고 바뀐 부분부터 다시 마운팅(Updating) 하기 시작한다
useEffect는 라이프사이클 관리가 아니라 side effect를 수행하는 데 사용되는 Hook이다
JavaScript running environment 에서 WEB API쪽으로 빠지는 것들이라 생각 하면 된다.
React의 사이드이펙트들도 대부분 웹 API를 사용하는 비동기 동작을 포함하고 있으며, 이러한 동작들은 useEffect 내에서 관리된다. 이는 useEffect가 비동기 동작에 대한 정리(cleanup)작업을 가능하게 하기 때문이다.
먼저 렌더링부터 이해해보자
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
위 코드에서 count
는 그저 숫자일 뿐이다. 마법의 데이터 바인딩 이나 워쳐나 프록시, 혹은 비슷한 그 어떤것도 아니다
const count = 42;
// ...
<p>You clicked {count} times</p>
// ...
처음으로 컴포넌트가 렌더링될 때, useState 로부터 가져온 count
변수는 숫자 0 이다. setCount(1)
을 호출하면, 다시 컴포넌트를 호출하고, 이 때 count 변수는 숫자 1 이 되는 것이다.
// 처음 랜더링 시
function Counter() {
const count = 0; // useState() 로부터 리턴
// ...
<p>You clicked {count} times</p>
<p>You clicked 0 times</p>
// ...
}
// 클릭하면 함수가 다시 호출된다
function Counter() {
const count = 1; // useState() 로부터 리턴
// ...
<p>You clicked {count} times</p>
// ...
}
// 또 한번 클릭하면, 다시 함수가 호출된다
function Counter() {
const count = 2; // useState() 로부터 리턴
// ...
<p>You clicked {count} times</p>
// ...
}
렌더링 결과물에 숫자 값을 내장하는 것에 불과하다. 이 숫자 값은 리액트를 통해 제공된다.
- setCount를 호출할 때, 리액트는 다른 count값과 함께 컴포넌트를 다시 호출한다
- 리액트는 가장 최신의 렌더링 결과물과 일치하도록 DOM을 업데이트 한다.
💡여느 특정 렌더링 시 그 안에 있는 count 상수는 시간이 지난다고 바뀌는 것이 아니다.
컴포넌트가 다시 호출되고, 각각의 렌더링마다 격리된 고유의 count값을 보는 것이다.
이벤트 핸들러는 어떨까? 아래의 컴포넌트 예제를 살펴보자.
function Counter() {
const [count, setCount] = useState(0);
function handleAlertClick() {
setTimeout(() => {
alert('You clicked on: ' + count);
}, 3000);
}
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}> Click me</button>
<button onClick={handleAlertClick}> Show alert </button>
</div>
);
}
이 컴포넌트는 3초 뒤에 count 값과 함께 alert를 띄워준다.
이 때 단계별로 이러한 과정을 실행한다면 어떤값이 나올까?
Show alert
버튼을 클릭한다답은 3이다. 왜이런 동작을 할까
앞서 count값이 매번 별개의 함수 호출마다 존재하는 상수값이라는 이야기를 했다. 우리의 함수는 여러번 호출되지만(렌더링마다 한 번씩), 각각의 렌더링에서 함수안의 count 값은 상수이자독립적인 값
(특정 렌더링 시의 상태)으로 존재 한다.
// 처음 랜더링 시
function Counter() {
// ...
function handleAlertClick() {
setTimeout(() => {
alert('You clicked on: ' + 0);
}, 3000);
}
<div>
<p>You clicked {count} times</p> // you clicked 0 times
<button onClick={() => setCount(count + 1)}> Click me</button>
<button onClick={handleAlertClick} /> // 0이 안에 들어있음
</div>
}
// 카운트를 3으로 늘린다 (모든 함수 클릭 할 때 마다 다시 호출)
function Counter() {
// ...
function handleAlertClick() {
setTimeout(() => {
alert('You clicked on: ' + 3);
}, 3000);
}
<div>
<p>You clicked {count} times</p> // you clicked 3 times
<button onClick={() => setCount(count + 1)}> Click me</button>
<button onClick={handleAlertClick} /> // 3이 안에 들어있음
</div>
}
// handleAlertClick 함수를 클릭한다
function Counter() {
// ...
function handleAlertClick() {
setTimeout(() => {
alert('You clicked on: ' + 3);
}, 3000);
}
<div>
<p>You clicked {count} times</p> // you clicked 3 times
<button onClick={() => setCount(count + 1)}> Click me</button>
<button onClick={handleAlertClick} /> // 3이 안에 들어있음
</div>
}
// 카운트를 5로 늘린다
function Counter() {
// ...
function handleAlertClick() {
setTimeout(() => {
alert('You clicked on: ' + 3); // you clicked on 3 (count를 늘렸어도 클릭한 시점에서의 값을 count 상수로 기억한다)
}, 3000);
}
<div>
<p>You clicked {count} times</p> // you clicked 5 times
<button onClick={() => setCount(count + 1)}> Click me</button>
<button onClick={handleAlertClick} /> // 5가 안에 들어있음
</div>
}
이펙트도 위의 양상과 다르지 않다.
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
어떻게 이펙트가 최신의 count 상태를 읽을까?
이전에 우리는 count는 특정 컴포넌트 렌더링에 포함되는 상수라고 배웠다.
이벤트 핸들러는 그 렌더링에 속한 count 상태를 본다. count는 특정 범위 안에 속하는 변수이기 때문
이펙트에도 똑같은 개념이 적용된다
변화하지 않는 이펙트 안에서 count 변수가 임의로 바뀐다는 뜻이 아니라 이펙트 함수 자체가 매 렌더링마다 별도로 존재한다.
각각의 이펙트 버전은 매번 렌더링에 속한 count 값을 본다
// 최초 랜더링 시
function Counter() {
// ...
useEffect(
// 첫 번째 랜더링의 이펙트 함수
() => {
document.title = `You clicked ${0} times`;
}
);
// ...
}
// 클릭하면 함수가 다시 호출된다
function Counter() {
// ...
useEffect(
// 두 번째 랜더링의 이펙트 함수
() => {
document.title = `You clicked ${1} times`;
}
);
// ...
}
// 또 한번 클릭하면, 다시 함수가 호출된다
function Counter() {
// ...
useEffect(
// 세 번째 랜더링의 이펙트 함수
() => {
document.title = `You clicked ${2} times`;
}
);
// ..
}
Promise<function>
이 되어 버리면 안되기 때문이다.// 잘못쓴 useEffect내부 async함수
useEffect(async() => {
const res = await fetch('어쩌구');
const data = await res.json();
setData(data);
// async함수는 무조건 Promise를 return한다고 했쭁 그래서 이건 틀린 상황입니다.
return () => {
//cleanup
}
}, [])
// 내부에서 async를 사용하려면 이렇게 써야합니다
useEffect(() => {
(async () => {
const res = await fetch('어쩌구');
const data = await res.json();
setData(data);
})();
return () => {
// cleanup
}
});