이전 글에 적어둔 내용을 우선 가져와보겠다.
Hooks 란 React v16.8 에 새롭게 도입된 기능으로 함수 컴포넌트에서도 상태 관리를 할 수 있는 useState, 랜더링 직후 작업을 설정하는 useEffect 등의 기능을 제공하여 기존의 함수 컴포넌트에서 할 수 없었던 작업을 할 수 있게 해주는 기능이다.
단순하게 함수인데 상태 값을 가질 수 있도록 해주는 기능이자 라이프사이클 구현을 할 수 있게 도와주는 기능이다.
단순하게 생각을 해보면 클래스형 컴포넌트가 문제가 있었거나 불편했기 때문일 것 같은데 이유를 찾아보니 아래와 같은 문제가 있었다고 한다.
그러면 함수 컴포넌트 + Hooks 는 어떠한 이점이 있을까? 이는 아래와 같다.
이러한 장점과 단점으로 인해서 React 공식페이지에도 함수 컴포넌트와 Hooks 를 활용한 함수 컴포넌트를 만들기를 권장한다. 아래는 함수 컴포넌트와 클래스형 컴포넌트의 차이, 그리고 왜 최근에 함수 컴포넌트를 사용하는지에 대한 이야기를 잘 설명해주고 있으니 참고하면 좋다.
이전 글의 정리 부분에 있던 라이프 사이클 그래프랑 약간은 다른 것을 볼 수 있다.
하지만 공통점을 찾아보면 마운팅, 업데이트, 언마운팅 의 과정으로 분리가 된 것을 볼 수 있다.
하나씩 확인을 해보자.
이전 글에서 useState 를 간단하게 만들어보면서 ‘아 이게 클로저와 전역 array 같은 기능들을 활용해서 만들어진 것일 수도..?’ 라는 생각이 들었을 수 있다. 그렇다면 내부적으로 이 state 들을 다루는 방법이나 순서는 React 에서 설정한 것일 것이다. 이 순서나 사용법을 지키지 않으면 array 내부가 망가질 수 있지 않을까?
내 추론이 맞는지 틀린지는 모르나 React 에서 hook 을 사용할 때 지켜야하는 규칙을 제공한다. 이는 아래와 같으니 hook 을 사용할 때 지키면서 사용할 수 있도록 해야 한다. 이 내용은 [React] Hooks란? 글을 참고했다.
이전 클래스 컴포넌트의 하단 블록은 마운팅시의 componentDidMount, 업데이트시의 componentDidUpdate, 언마운팅시의 componentWillUnmonut 이렇게 3개의 라이프 사이클 메서드가 있던 것을 생각하면 이번에는 useEffect 와 useLayoutEffect 이렇게 두 개의 hook 이 그 역할을 하는 것을 볼 수 있다.
그렇다면 useEffect 는 다음과 같은 기능을 할 수 있다고 정리할 수 있다.
위 상황들을 코드와 함께 알아보도록 한다. 코드와 글은 벨로퍼트와 함께하는 모던 리엑트 를 참고했다.
일단 기본 구조부터 알아보면 다음과 같다.
// 기본 구조 useEffect(function, deps)
useEffect(() => {
console.log('컴포넌트가 화면에 나타남');
return () => { // cleanup 함수
console.log('컴포넌트가 화면에서 사라짐');
};
}, []);
useEffect 를 사용 할 때에는 첫 번째 파라미터에는 함수, 두 번째 파라미터에는 의존값이 들어있는 배열 (deps)을 넣는다. 만약에 deps 배열을 비우게 된다면, 컴포넌트가 처음 나타날때에’만’ useEffect 에 등록한 함수가 호출된다. (useEffect 는 컴포넌트가 마운트 됬을 때 자동으로 작동한다.)
useEffect 에서는 함수를 반환 할 수 있는데 이를 cleanup 함수라고 부른다.
cleanup 함수는 useEffect 에 대한 뒷정리를 해주는 역할로, deps 가 비어있는 경우에는 컴포넌트가 사라질 때 cleanup 함수가 호출된다.
그러면 useEffect 를 통해 마운팅을 할 때는 어떠한 작업을 주로 해주면 좋을까? 이는 아래와 같다.
언마운트를 할 때는 어떠한 작업을 할까? 이는 다음과 같다.
업데이트시에는 본인이 원하는 상황을 정하고 알려주면 된다… 라고 하려 했는데 어떻게 하면 업데이트시 마다 동작하게 만들까?
앞서 말했던 deps 에 원하는 값을 넣으면 된다. deps 에 특정 값을 넣어둔다면 해당 값들이 바뀔 때에도 호출이 되게 할 수 있다.
useEffect(() => {
console.log('user 값이 설정됨');
console.log(user);
return () => {
console.log('user 가 바뀌기 전..');
console.log(user);
};
}, [user]);
useEffect 안에서 사용하는 상태나, props 가 있다면, useEffect 의 deps 에 넣어주어야 한다. 그렇게 하는게, 규칙이다. 만약 useEffect 안에서 사용하는 상태나 props 를 deps 에 넣지 않게 된다면 useEffect 에 등록한 함수가 실행 될 때 최신 props / 상태를 가르키지 않게 된다.
한 가지 궁금한 상황이 있다. 만약 deps 에 빈 어레이도 안넣어주면 어떠한 일이 일어날까?
이러한 경우에는 컴포넌트가 리렌더링 될 때마다 호출이 된다.
useEffect 는 들어봤지만 useLayoutEffect 는 이번 학습을 통해서 처음 듣게 되었다. 아마 대부분의 나와 같은 찍먹 입문자들은 동일하게 처음 보는 것일 거다. 일단 모양을 보면서 대략 유추를 해보자.
useLayoutEffect(() => {
effect
return () => {
cleanup
};
}, [input])
뭐지 싶을 수 있다. 맞다 useEffect 와 동일해보인다. 그러면 어떠한 차이가 있을까?
일단 위에서 이야기했듯 useEffect 는 componentDidMount, componentDidUpdate, componentWillUnmonut 의 역할을 한다고 했다. 이 라이프 사이클 메서드의 특징은 DOM 의 레이아웃 배치와 페인트가 끝난 후 호출이 이루어진다. useEffect 또한 마운트, 업데이트, 언마운트 작업이 끝난 이후에 이루어진다. 이러한 상황속 만약 상태 값이 useEffect 에 의존한다면 사용자 경험에 불편함을 줄 수 있다. 예를 통해 봐보자.
import { useEffect, useState } from "react";
function App() {
const [age, setAge] = useState(0);
const [name, setName] = useState("");
useEffect(() => {
setAge(25);
setName("지훈");
}, []);
return (
<>
<div className="App">{`그의 이름은 ${name} 이며, 나이는 ${age}살 입니다.`}</div>
</>
);
}
export default App;
이러한 코드가 있다고 했을 때 처음 DOM 의 레이아웃 배치와 페인트가 이루어졌을 때는 ‘그의 이름은 이며, 나이는 살 입니다.’ 라는 글이 화면에 출력될 것이다. 물론 잠깐의 순간이겠지만 해당 상태가 유지되고, useEffect 이후에 ‘그의 이름은 지훈 이며, 나이는 25살 입니다.’ 가 될 것이다.
위의 예제에서는 저 딜레이가 크지 않아서 불편한 부분이 적지만 useEffect 내부에서 다루는 state 가 많으면 그 딜레이는 더 커질 것이다. 이러한 불편을 해결하기 위해서 나온 것이 useLayoutEffect 이다.
useLayoutEffect 는 브라우저가 DOM 을 그리기 전에 이펙트를 수행하고 이후 작업을 이어서 한다.
정리하면 아래와 같다.
Hooks 하면 떠오르는 대표 hook 이자 가장 기본적인 hook 이다.
말 그대로 state, 동적인 값을 사용한다고 생각하면 된다.
간단한 사용법이나 예제는 다른 글에서도 너무 잘 나와있으며 해당 부분을 정리하기에는 의미가 크게 없다고 생각이 된다. 혹여나 기초적인 사용법을 알고 싶으신 분이 있다면 벨로퍼트님 블로그를 참고하면 큰 도움이 될 것이다.
이번에 다룰 내용은 학습을 하면서 처음 알게된 내용을 다루려고 하는데 먼저 출처는 **[ React ] useState는 어떻게 동작할까 이다.
이 두 가지를 다뤄볼 것이며 출처에서 나온 내용을 간단하게 다뤄보려 한다. (정말 정리를 잘하신 글이기 때문에 한번씩 보는 것을 추천드린다.)
vscode 나 다른 IDE 를 사용해서 useState 가 선언된 곳을 찾아보면 node_modules/react/cjs/react.development.js 인 것을 확인할 수 있다.
내부의 내용을 확인하면 대충 dispatcher 라는 인스턴스를 생성하고, 우리가 입력한 초기 값을 dispatcher.useState 에 전달한 후 반환값을 보내준다.
그러면 resolveDispatcher 는 어디에 있는지 찾아보자.
동일한 파일에 존재하며 useState 위를 보면 찾을 수 있다. 보면 이 또한 ReactCurrentDispatcher 의 current 속성을 통해서 dispatcher 를 만들고 예외처리를 하는 걸 볼 수 있다.
귀찮을 수 있지만 한 번 더 위로 가본다.
이것이 ReactCurrentDispatcher 로 전역으로 선언된 것을 볼 수 있다. 이 객체에 있는 current 속성에 (지금은 null 이지만) dispatcher 가 담길 예정인 것 같다. 더 이상 따라올라갈 코드가 없으니 여기서 중단한다.
위에서 뒤져본 결과로 알 수 있는 것은 아래와 같다.
이는 앞 글에서 잠깐 설명한 클로저 개념이랑 유사하다고 생각하면 된다.
각 상황별로 확인을 해본다. 이 내용은 출처에 있는 내용을 거의 그대로 사용했다.
웹이 로딩되고 최초로 컴포넌트 함수가 호출
setState 호출
setState가 실행되어 리렌더링이 발생
즉, setState 함수는 자신과 함께 반환된 변수를 변경시키는게 아니라, 다음 useState가 반환할 react 모듈의 _value 를 변경시키고, 컴포넌트를 리렌더링 시키는 역할을 한다. 이후 변경된 값은 useState가 가져온다.
클래스형 컴포넌트에 대한 이해를 가지고 이를 함수 컴포넌트의 라이프 사이클과 비교하면서 하니 이해가 더욱 쉬운 부분이 있었다. 또한 좋은 글을 발견해서 useState 에 대한 이해도가 조금은 높아진 것 같아서 무언가 뿌듯했고 좋은 글 작성해주신 DD 님께 감사하다. 남은 Hooks 라이프 사이클 내용은 다음 글에서 이어서 하기로 한다.