React에서 Hook이란, 함수형 컴포넌트에서 사용되는, state와 관련된 기술들을 모아서 일컫는 말이며 Hook은 class를 작성하지 않고도 state와 다른 React의 기능들을 사용할 수 있게 해준다. 대표적으로 useState()
와 useEffect()
가 있다.
Hook은 함수 컴포넌트에서 React state와 생명주기 기능(lifecycle features)을 “연동(hook into)“할 수 있게 해주는 함수입니다. Hook은 class 안에서는 동작하지 않습니다. 대신 class 없이 React를 사용할 수 있게 해주는 것입니다.
React Hook 이 나오기전 클래스 컴포넌트의 componentDidMount ()
나 componentDidUpdate ()
와 같은 Life Cycle Hook 함수를 사용해 Side Effect를 처리했다.
// Hook 등장 전의 Side Effect 처리
import React, { Component } from "react";
class UserListClass extends Component {
state = {
loading: true,
users: [],
};
componentDidMount() {
fetch("https://jsonplaceholder.typicode.com/users")
.then((response) => response.json())
.then((users) => this.setState({ users, loading: false }));
}
render() {
const { loading, users } = this.state;
if (loading) return <div>Loading...</div>;
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
}
예시를 살펴보자면 외부 API 연동을 통해 사용자 목록을 조회하는 React 컴포넌트가 있다. 이 클래스 컴포넌트는 로딩 여부와 사용자 목록을 this.state
필드의 loading
과 users
속성에 각각 저장하고 있다.
이 컴포넌트가 최초 렌더링될 때는 loading
의 초기 값이 true
라서 Loading...
이라는 메시지가 화면에 나타난다. 하지만 곧 componentDidMount()
함수가 호출되면 API가 호출되어 그 응답 결과가 users
속성에 할당되고 loading
이 false
로 업데이트 된다. 따라서 화면에 있던 Loading...
메시지는 사라지고, 대신 사용자 이름들이 나타나게 된다.
이런 클래스 기반 컴포넌트는 함수 기반 컴포넌트에 비해 복잡하고 따라서 오류가 발생하기 쉽고 유지 보수가 힘들기 때문에 많은 React 개발자들은 이렇게 간단한 Side Effect 구현 조차도 클래스 기반 컴포넌트로 작성해야 한다는 점에 대해서 불평했고 때문에 React Hook의 useEffect
가 나오게 되었다.
useEffect() 함수는 React component가 렌더링 될 때마다 특정 작업(Sied effect)을 실행할 수 있도록 하는 리액트이며 이를 사용하면 함수형 컴포넌트에서도 클래스형 컴포넌트에서 사용했던 생명주기 메서드를 사용할 수 있게 되었다.
import React, { useEffect } from "react";
useState와 마찬가지로 함수 불러오기를 통해 함수를 불러와야만 사용이 가능하다.
useEffect(function, deps)
실행하고자 하는 함수
배열형태, fuction을 실행시킬 조건
useEffect
의 첫번째 인자는 함수이다. 해당 함수내에서 side effect를 실행하면 되며 실행 조건이 있다.
새롭게 컴포넌트가 렌더링 될 때 Effect Hook이 실행된다고 볼 수 있다.
조건부 effect 발생을 실행시키기 위해 필요한 useEffect
의 두번째 인자는 배열이다. 이 배열은 조건을 담고 있는데 여기서 조건은 boolean형태의 표현식이 아니라 어떤 값의 변경이 일어날 때를 의미한다. 배열엔 어떤 값의 목록이 들어가며 종속성 배열 (dependency array) 이라고 부른다.
이 때문에 useEffect
는 화면에 첫 렌더링될 때, 그리고 종속성 배열의 value
값이 바뀔 때 라는 실행조건을 가지게 된다. 따라서 모든 state가 아닌 특정 state에 관해서만 Side effect를 실행시킬 수 있다.
useEffect
에 대해서 익혔으니 이제 이를 응용해 여러 경우에서의 사용법을 알아보자.
useEffect(() => {
console.log("렌더링 될 때마다 실행");
});
deps 부분을 생략시 해당 컴포넌트가 렌더링 될 때마다 useEffect
가 실행된다.
useEffect(() => {
console.log("맨 처음 렌더링될 때 한번 만 실행");
},[]);
deps 부분에 빈배열을 넣을시 해당 컴포넌트가 처음 생성시에만 useEffect
가 실행된다.
useEffect(() => {
console.log(name);
console.log("name이라는 값이 업데이트 될 때만 실행");
},[name]);
특정값이 업데이트 될 때만 실행하고 싶을 땐 deps위치의 배열안에 실행조건을 넣어주어야한다. 업데이트뿐만 아니라 마운터 될 때도 실행되므로 업데이트 될 때만 실행된다.
const mounted = useRef(false);
useEffect(() => {
if (!mounted.current) {
mounted.current = true;
} else {
console.log(name);
console.log("업데이트 될 때만 실행");
};
},[name]);
useRef
는 리렌더링 하지 않고 컴포넌트의 속성만 조회&수정한다. 이를 이용해 컴포넌트 안의 변수를 관리하면 리렌더링을 막을 수 있다.
useEffect(() => {
console.log("컴포넌트 나타남");
console.log(name);
return () => {
console.log("cleanUP 함수");
};
});
useEffect
는 함수를 반환할 수 있는데 이 함수를 cleanUP
이라고 한다. 만약 unmount 될 때만 cleanUp
함수를 실행시키고 싶다면 deps에 빈배열을 넣으면 된다. 만약 특정 값이 업데이트되기 직전에 cleanUP 함수를 실행시키고 싶다면 deps에 해당 값을 넣어주면 된다.
// 예시
import React, { useEffect, useState } from "react";
function UseEffect() {
const [name, setName] = useState("초기 닉네임");
useEffect(() => {
console.log("컴포넌트 나타남");
console.log(name);
return () => {
console.log("cleanUp 함수");
};
});
const onClick = () => {
setName("닉네임 변경");
};
return (
<div>
{name} <button onClick={onClick}>변경</button>
</div>
);
}
export default UseEffect;