useState
의 성공적인 안착은 팀에 새로운 활력을 불어넣었다. 하지만 그들은 축배를 들 시간이 없었다. 클래스 컴포넌트가 하던 일의 절반만이 함수로 옮겨왔을 뿐이었다.
소피가 회의실 화이트보드에 클래스 컴포넌트의 생명주기 메서드들을 다시 나열했다.
constructor
:useState
가 그 역할을 일부 대체했다.render
: 함수형 컴포넌트의 반환 값이 그 자체로render
였다.setState
:useState
가 반환하는 두 번째 요소, 즉set
함수가 그 역할을 했다.
하지만 문제는 남아있는 것들이었다.
componentDidMount
, componentDidUpdate
, componentWillUnmount
.
이 메서드들은 단순히 상태를 바꾸는 것을 넘어, 리액트의 통제권 밖의 세상과 상호작용하는 코드들을 담고 있었다.
서버에 데이터를 요청하고, 타이머를 설정하고, window
객체에 이벤트 리스너를 붙이고, 외부 라이브러리를 연동하는 행위.
함수형 프로그래밍의 관점에서, 이것들은 모두 ‘부수 효과(Side Effects)’였다.
함수형 컴포넌트는 본질적으로 렌더링될 때마다 실행되는 ‘순수한 계산’에 가까워야 했다. 만약 함수 본문 안에서 직접 API를 호출한다면, 그 호출은 렌더링이 일어날 때마다, 심지어는 아직 화면에 반영되기도 전에 무분별하게 실행될 터였다. 그것은 재앙이었다.
“부수 효과를 담을 안전한 그릇이 필요합니다.”
댄이 말했다.
“렌더링 계산이 모두 끝나고, 리액트가 실제 DOM을 업데이트한 ‘후에’ 실행을 보장해주는 공간 말이죠.”
그것이 바로 componentDidMount
와 componentDidUpdate
가 하던 역할이었다.
팀은 이 역할을 할 새로운 훅의 이름을 고민했다.
useSideEffect
? 너무 직접적이고, ‘부수 효과’라는 단어는 부정적인 뉘앙스를 풍겼다.
useAfterRender
? 역할은 명확하지만, 이름이 너무 길었다.
오랜 논의 끝에, 그들은 useEffect
라는 이름을 선택했다.
컴포넌트의 렌더링 자체가 주된 ‘효과(Effect)’이고, 그 외의 부가적인 효과들을 처리한다는 의미를 담았다. useState
와 마찬가지로, use
라는 접두사를 붙여 통일성을 유지했다.
useEffect
의 기본 구조는 단순했다.
개발자는 부수 효과를 일으키는 코드를 담은 함수를 useEffect
에 인자로 넘긴다.
function WelcomeMessage() {
const [user, setUser] = useState(null);
// useEffect에 함수를 전달한다.
useEffect(() => {
// 이 안의 코드는 렌더링이 완료된 후에 실행된다.
fetchUser().then(fetchedUser => {
setUser(fetchedUser);
});
});
if (!user) {
return <p>Loading...</p>;
}
return <h1>Hello, {user.name}</h1>;
}
리액트는 이 함수를 어딘가에 저장해두었다가, 렌더링을 모두 마치고 브라우저 화면이 업데이트된 직후, 비로소 이 함수를 실행시켜 주었다. 이 간단한 약속만으로, 개발자들은 부수 효과를 렌더링 로직과 안전하게 분리할 수 있게 되었다.
이것으로 componentDidMount
의 역할은 해결된 것처럼 보였다.
하지만 곧바로 새로운 질문이 떠올랐다.
“만약 이 컴포넌트가 화면에서 사라진다면요?”
한 엔지니어가 문제를 제기했다.
“클래스에서는 componentWillUnmount
에서 정리(cleanup) 작업을 했습니다. 만약 useEffect
에서 외부 라이브러리를 구독했다면, 그 구독은 어디서 해지해야 하죠?”
useState
가 상태의 ‘시작’과 ‘갱신’을 다루었다면, useEffect
는 부수 효과의 ‘시작’과 ‘끝’을 모두 책임져야 했다.
새로운 훅은 아직 절반의 기능밖에 갖추지 못한 셈이었다.
팀은 useEffect
가 부수 효과를 시작하는 것뿐만 아니라, 그것을 깔끔하게 정리하는 방법까지 제공해야 한다는 것을 깨달았다. 두 번째 퍼즐 조각은 맞춰졌지만, 그 조각의 나머지 절반을 어떻게 디자인할 것인가에 대한 새로운 고민이 시작되었다.