0. React에는 왜 Hooks가 있을까? (공통 글)
리액트는 라이브러리이다.
기존에 HTML, CSS, Javascript로만 구성된 프로젝트에서 JSX를 해석해 줄 Babel만 있으면 부분적으로 도입할 수 있다는 이야기다.
처음에 리액트는 클래스형 컴포넌트가 많이 쓰였다. 여러 가지 이유가 있었지만, 함수형 컴포넌트는 내부적으로 상태를 가지고 있을 수 없기 때문이었다.
Q. 왜 함수형 컴포넌트는 상태를 저장할 수 없나요?
A. 우리가 무언가를 '저장'한다고 생각해봅시다. 변수를 선언하지 않고 저장하는 방법이 있을까요?
없습니다. 클래스는 그 자체가 객체로 태어나면서 변수 안에 데이터를 가지고 있을 수 있습니다. 하지만 함수는 어떤가요? 함수는 실행형 문구입니다. 그냥 한 번 '실행'되고 끝나는 것이죠. 무언가를 저장할 수 없습니다.
이런 단점을 보완하기 위해서 2019년 페이스북은 리액트 16.8 버전에 Hooks를 도입한다.
Hooks는 함수형 컴포넌트에서 상태를 보존할 수 있는 방법을 제공했고, 그 외에도 컴포넌트의 생애주기(life-cycle)에서 단계별로 여러 가지 기능을 수행할 수 있도록 방법을 제공했다.
리액트에서 제공하는 Hooks는 기본적으로 3가지(useState, useEffect, useContext)이며, 그 외에는 10가지 추가 Hooks가 있고 라이브러리 형태로 2가지를 더 제공한다.
1. useEffect 설명
useEffect는 2가지 역할을 합니다.
1. 해당 컴포넌트가 모두 렌더링된 이후에 실행되는 문구입니다.
(되게 중요합니다. 레이아웃 배치와 렌더링을 모두 완료한 후에 실행합니다.)
2. 1을 만족하면서도, 특정 값이 변했을 때 실행되게 할 수도 있습니다.
아무래도 기능이 2가지다 보니 헷갈릴 수 있는데,
예제코드를 통해서 배워보겠습니다.
2. 초기 코드와 첫 번째 기능
이 글에서 알려드릴 것은 useEffect입니다.
초기 코드는 이렇습니다.
import React, { useState } from "react";
import "./App.css";
function App() {
const [count, setCount] = useState(1);
const handlerCountUpdate = () => {
setCount(count + 1);
};
return (
<>
<button onClick={handlerCountUpdate}>Update</button>
<span> count: {count} </span>
</>
);
}
export default App;
⚠ 저는 빌드를 Vite로 하기 때문에 실행했을 때 화면이 조금 다를 수 있습니다.
그냥 전형적인 카운트 올리는 예제입니다.
이제 이정도 코드는 보자마자 지겹다고 느껴져야 리액트를 해본 겁니다.
카운터에 더해서 input에 값을 넣을 때마다 화면이 그려질 수 있도록 onChange에 함수를 넣어줍시다.
그리고 input 태그 내의 값을 바뀔 때마다 세팅을 해줍니다.
import React, { useEffect, useState } from "react";
import "./App.css";
function App() {
const [count, setCount] = useState(1);
const [name, setName] = useState("");
const handlerCountUpdate = () => {
setCount(count + 1);
};
useEffect(() => {
console.log("렌더링 완료");
});
const handleInputChange = (e) => {
setName(e.target.value);
};
return (
<>
<div>
<button onClick={handlerCountUpdate}>Update</button>
<span> count: {count} </span>
</div>
<div>
<input type="text" value={name} onChange={handleInputChange} />
<span>name: {name}</span>
</div>
</>
);
}
export default App;
이렇게 되면 화면은 언제 새로 그려지나요?
1. 버튼을 누를 때마다 숫자를 늘려야 하니까 화면을 새로 그린다.
2. input 태그에 글을 입력할 때마다 옆에 문자를 추가해야 하기 때문에 화면을 새로 그린다.
이 경우에는 저 2가지 일이 발생할 때마다 화면이 그려지는 겁니다.
화면이 그려질 때 콘솔을 보면 useEffect에 적어놓은 함수가 실행되는 것을 알 수 있습니다.
그러니까 요약하자면,
순서
1. 리액트 컴포넌트가 탄생
-> 2. 컴포넌트에 있는 기존 메모리 저장값, 기능들 모두 수행
-> 3. 마지막에 useEffect에 있는 기능 수행
이렇게 되는 겁니다.
3. useEffect의 두 번째 기능
앞서 useEffect는 2개의 기능을 한다고 했습니다.
두 번째는 "특정 값이 변할 때 실행"되는 것입니다.
바로 앞의 첫 번째 기능은 큰 단점이 있습니다.
만약에 useEffect안에 외부에서 3,000개의 데이터를 가지고 오는 코드를 적었다고 생각해 봅시다.
counter 버튼을 클릭할 때마다, input 태그에 글을 적을 때마다 3,000개씩 데이터를 새로 가져옵니다.
엄청나게 비효율적일 뿐만 아니라 해당 컴포넌트 전체가 다 느려지겠죠.
이럴 때 특정 값이 변할 때만 useEffect를 실행시킬 수 있습니다.
예시 코드로 알아봅시다.
import React, { useEffect, useState } from "react";
import "./App.css";
function App() {
const [count, setCount] = useState(1);
const [name, setName] = useState("");
const handlerCountUpdate = () => {
setCount(count + 1);
};
useEffect(() => {
console.log("렌더링 완료 버전2");
}, [count]); // <--- 여기가 바뀜
const handleInputChange = (e) => {
setName(e.target.value);
};
return (
<>
<div>
<button onClick={handlerCountUpdate}>Update</button>
<span> count: {count} </span>
</div>
<div>
<input type="text" value={name} onChange={handleInputChange} />
<span>name: {name}</span>
</div>
</>
);
}
export default App;
기존 useEffect의 구문에서 2번째 파라미터로 배열을 넣었습니다.
이렇게 되면 useEffect는 처음 예시코드와 달리 완전히 다르게 동작합니다.
최초에 해당 컴포넌트가 그려질 때는 1회 실행됩니다. (처음에 데이터 3,000개는 가져와야 하잖아요..)
그러고 나서는 해당 배열에 있는 값이 변할 때만 실행됩니다.
이 경우에는 count라는 변수를 추적합니다.
그렇기 때문에 더 이상 input 태그에 글을 넣는다고 해서 useEffect가 실행되지 않습니다.
4. 요약
기능은 다 살펴봤습니다.
모든 컴포넌트가 그려진 이후에 실행할 구문을 넣는다. 그리고 두번째 파라미터에 있는 배열에 있는 값을 추적한다.
그래서 useEffect의 기본 형태는 이렇습니다.
useEffect(setup, dependencies?)
?는 null여부가 불분명하다는 뜻으로 없어도 된다는 뜻입니다.
참고:
1. 기존 Effect Hook의 글
https://reactjs.org/docs/hooks-effect.html
2. 현재 베타 글
https://beta.reactjs.org/reference/react/useEffect