Hooks에 대한 기초 지식은 구글링하면 아주 잘 정리된 글들이 아주 많습니다. 기본적인 useState나 useEffect, 함수형 프로그래밍 등은 제가 직접 쓰고 또 공부를 위해 제 글을 다시 제가 읽는 것 보다도 계속 다른 개발자분들의 글을 계속해서 읽는게 훨씬 효율적이겠다는 생각이 들었습니다. 그러면 Hooks에 관해서 어떤 글을 작성할까 고민을 하다가 저는 조금은 다른 방향에서 React Hooks에 접근해 보겠습니다.
Class 기반 React의 Lifecycle
일단 소프트웨어 개발에서 Lifecycle은 어떤 프로그램이 실행되고 종료되는 과정을 의미합니다. React에서는 React 웹 어플리케이션이 실행되고(Mount) 종료되기(Unmount)까지의 과정을 세세하게 나누어서 컨트롤 할 수 있었습니다. 이 역시 구글에 잘 정리된 글이 많습니다만 간단하게 설명하자면
constructor ➡ componentWillMount ➡ componentDidMount ➡ componentWillUnmount
그리고 컴포넌트의 갱신에 있어서 componentWillUpdate와 componentDidUpdate가 있습니다.
constructor는 객체지향 언어들은 대부분 가지고 있는 메소드이고 나머지는 전부 리액트만의 라이프 사이클 메소드에 해당됩니다.
import React from "react";
export class Lifecycle extends React.Component {
constructor() {
super();
console.log("constructor");
}
componentWillMount() {
console.log("will mount!");
}
componentDidMount() {
console.log("Did mount!");
}
render() {
console.log("render");
return (
<div>
<h1>OMG!</h1>
</div>
);
}
}
위와 같이 class 기반의 리액트 코드를 작성하고 콘솔을 확인하면
위와 같이 어떤 순서대로 리액트 어플리케이션이 진행되는지 확인 할 수 있게 됩니다. 위 lifecycle 메소드 이외에도 리액트에는 여러가지 메소드가 존재합니다.
Hooks의 Lifecycle
Hooks에서는 useEffect
를 통해 Lifecycle을 관리합니다. useEffect
는 클래스 기반 Lifecycle 메소드에서 componentDidMount
와 componentDidUpdate
, componentWillUnmont
세 가지 역할을 할 수 있습니다.
import React, {useState, useEffect} from 'react';
export const HooksLifeCycle = () =>{
useEffect(()=>{
console.log('component did mount with useEffect!')
})
return(
<div>
<h1>Hello?</h1>
</div>
)
}
위와 같이 리액트 코드를 작성하고 콘솔을 확인하면 리액트 컴포넌트가 마운트 된 이후에 콘솔창에 메시지를 보여주기 때문에 componentDidMount의 역할을 하는 것을 볼 수 있습니다.
import React, { useState, useEffect } from "react";
export const HooksLifeCycle = () => {
const [number, setNumber] = useState(0);
useEffect(() => {
console.log("component did mount with useEffect!");
});
return (
<div>
<h2>number is {number}</h2>
<button
onClick={() => {
setNumber(number + 1);
}}
>
Increment
</button>
</div>
);
};
이제 다음과 같이 코드를 수정합시다. 위 코드는 버튼을 클릭하면 number를 1씩 올려주는 코드입니다. 사실 useEffect가 받는 파라미터는 useEffect(<function>, <Array>);
인데 <function>
은 컴포넌트가 render 또는 re-render 되었을 때 실행하고 싶은 함수를 의미하고 에는 어떤 state가 변화되었을 때 컴포넌트를 re-render할지 그 state를 담는 Array
형태의 파라미터입니다.
리액트 어플리케이션을 실행하고 버튼을 누르면 누를 때마다 계속 컴포넌트가 re-render 되기 떄문에 “component did mount with useEffect!”
이 문구가 계속 콘솔에 찍힙니다.
그러면 위 코드에서
useEffect(() => {
console.log("component did mount with useEffect!");
});
이 부분을
useEffect(() => {
console.log("component did mount with useEffect!");
}, []);
이렇게 빈 브라켓으로 두 번째 파라미터를 주게 되면 이 의미는 어떠한 state가 변화되더라도 컴포넌트를 re-render하지 않겠다 라는 의미입니다.
따라서 버튼을 아무리 누르더라도 “component did mount with useEffect!” 문구가 나오지 않게 됩니다.(render되지 않았으므로 mount되지도 않는다.)
그러면 다시 코드를
useEffect(() => {
console.log("component did mount with useEffect!");
}, [number]);
이렇게 바꾸게 되면 이 의미는 number
라는 state가 변화되면 컴포넌트를 re-render하겠다는 의미가 되고 그러면 버튼을 누를때마다 콘솔에 다시 마운트되었다는 문구가 찍힙니다.
componentWillUnmount
는 컴포넌트가 수명을 다하고 사라질때 어떤 행동을 하는 것을 의미합니다. 이는 useEffect의 return값을 설정하면 됩니다.
import React, { useState, useEffect } from "react";
export const HooksLifeCycle = () => {
const [number, setNumber] = useState(0);
useEffect(() => {
console.log("component did mount with useEffect!");
return () => {
console.log("I'm dying...");
};
}, [number]);
return (
<div>
<h2>number is {number}</h2>
<button
onClick={() => {
setNumber(number + 1);
}}
>
Increment
</button>
</div>
);
};
다음과 같이 return 에 함수를 주면 컴포넌트가 unmount될 때 함수를 실행합니다. 일반적으로 eventListner
의 역할을 다하면 다시 remove할때 이 useEffect
의 return값을 이용합니다 .
Hooks는 state관리와 lifecycle을 함수형 프로그래밍의 형태에서 가능하도록 개발 되었습니다. 덕분에 복잡한 object형식의 state와 setState, this와 bind등 클래스 기반 프로그래밍의 귀찮은 부분들을 전부 버리고 짧고 간결한 코드로 리액트 코드를 작성할 수 있게 되었습니다. 이제부터 작성하는 모든 리액트 앱은 리액트 훅스로 작성하도록 페이스북 리액트 개발팀은 권장하고 있지만 이전의 작성된 클래스 기반의 리액트 코드를 굳이 훅스로 변경할 필요는 없다고 했습니다. react hooks는 react 코드를 편하고 직관적이고 간결하게 함수형 프로그래밍을 통해서 만들어줄 뿐 기능상의 확장은 없기 때문입니다.