Hook
이란 특별한 함수를 말한다. 예를들어, useState
는 state를 함수 컴포넌트 안에서 사용할 수 있게 해준다.
import { useState } from 'react'; // useState 사용하기 위해 임포트
function Example() {
//...
}
import { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
useState()
인자로 넘겨주는 값은 state의 초기값이다. 여기서는 0이 초기값이다. useState
는 state 변수와 해당 변수 상태를 변경하는 함수 이 두가지 쌍을 반환한다.
(클래스 컴포넌트의 this.state.count
와 this.setState
와 유사하다.)
위에 예시에서, count
라는 변수(초깃값 0)와, 이 변수의 값을 변경하는 setCount
함수 이 두 가지 쌍이 반환된다.
함수 컴포넌트는 바로 호출이 가능하다.
<p>You clicked {count} times</p>
count
변수를 직접 사용할 수 있다. (클래스에서는 this.state.count
로 사용한다.)
this
를 호출하는 것 없이, setCount
함수로 count
값의 상태를 변경할 수 있다.
<button onClick={() => setCount(count + 1)}>
Click me!
</button>
LifeCycle
LifeCycle API
컴포넌트가 DOM 위에 생성될 때 [컴포넌트가 처음 화면에 렌더링]
컴포넌트가 DOM 위에 사라지기 전 [컴포넌트가 화면에 지워짐]
데이터가 변경되어 상태를 업데이트 한 후 [데이터가 변경될 때]
React 컴포넌트 rendering 할 때 나오는 메소드
constructor
componentWillMount (레거시)
render
componenetDidMount
React 상태 값 변경시 나오는 메소드
componentDidUpdate
컴포넌트 제거시 나오는 메소드
componentWillUnmount
LifeCycle API 메소드는 클래스 컴포넌트에서만 사용이 가능하다. 함수 컴포넌트에서는 useEffect Hook
이 그 역할을 대신한다.
useEffect
함수는 리액트 컴포넌트가 렌더링 될 때마다 특정 작업을 실행할 수 있도록 하는 Hook
을 말한다.useEffect가 하는 일?
useEffect Hook
을 이용하여 우리는 React에게 컴포넌트가 렌더링 이후에 어떤 일을 수행해야 하는 지를 말해줄 수 있다.effect
라고 부름) DOM 업데이트를 수행한 이후에 불러낸다. +) useEffect는 Hook을 componentDidMount
와 componenetDidUpdate
, componentWillUnmount
가 합쳐진 것으로도 생각할 수 있다.
Side Effect
는 실행 중에 어떤 객체를 접근해서 변화가 일어나는 행위를 말한다.Side Effect
이다.useEffect
는 함수 컴포넌트에서 Side Effect
를 처리하기 위해 사용한다.useEffect
함수를 통해 분리한다.import { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// componentDidMount(렌더링), componentDidUpdate(업데이트)와 같은 방식으로
useEffect(() => {
// 브라우저 API를 이용하여 문서 타이틀을 업데이트한다.
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Side Effect 종류
정리 (clean-up)가 필요한 것 / 그렇지 않은 것
(React가 DOM으로 업데이트 한 뒤 추가로 코드를 실행해야 하는 경우)
Class를 사용하는 경우
React의 클래스 컴포넌트에서 render
메서드 그 자체는 side effect
를 실행시키지 않는다.
(effect
를 수행하는 시기는 React가 DOM을 업데이트 한 이후이다.)
class Example extends React.Component {
constructor(props) {
super(props);
this.state =
{
count: 0
};
}
componentDidMount() { // side effect
document.title = `You clicked ${this.state.count} times`;
}
componentDidUpdate() { // side effect
document.title = `You clicked ${this.state.count} times`;
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>Click me</button>
</div>
);
}
}
React class에서 side effect
를 componentDidMount
와 componentDidUpdate
에 두는 것은 effect
를 수행하는 시점이 리액트가 DOM을 업데이트 한 이후이기 때문이다.
이는 컴포넌트가 이제 막 마운트된 단계인지, 아니면 업데이트되는 단계인지에 상관없이 같은 side effect
를 수행해야 하기 때문이다. 렌더링 이후에는 항상 같은 코드가 수행되기를 바라는 것. -> 하지만 React 클래스 컴포넌트는 그러한 메서드를 갖고있지 않다.
따라서 함수를 별개의 메서드로 뽑아낸다고 해도 여전히 두 장소에서 함수를 불러내야 하는 코드 중복이 일어난다.
Hook(useEffect)을 사용하는 경우
import { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You Clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
위의 경우에는 effect
를 통해 문서 타이틀을 지정하지만, 이 외에도 데이터를 가져오거나 다른 명령형(imperative) API를 불러내는 일도 할 수 있다.
useEffect
를 컴포넌트 안에서 불러내는 이유는 컴포넌트 내부에 둠으로써 effect
를 통해 count
라는 state 변수(혹은 그 어떤 prop)에 접근할 수 있기 때문이다. 함수 범위 안에 존재하므로 특별한 API 없이도 값을 얻을 수 있다. -> Hook은 React에 한정된 API를 고안하는 것보다 자바스크립트가 이미 갖고 있는 방법을 이용하여 문제를 해결한다.
또한, useEffect
는 렌더링 이후 매번 수행된다. 리액트는 effect
가 수행되는 시점에 이미 DOM이 업데이트 되었음을 보장한다.
Class를 사용하는 경우
componentDidMount
에 구독(subscription)을 설정한 뒤 componentWillUnmount
에서 이를 정리한다.class FriendStatus extends React.Component {
constructor(props) {
super(props);
this.state = { isOnline: null };
this.handleStatusChange = this.handleStatusChange.bind(this);
}
componentDidMount() { // 구독 설정
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentWillUnmount() { //정리
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
handleStatusChange(status) {
this.setState({
isOnline: status.isOnline
});
}
render() {
if (this.state.isOnline === null) {
return 'Loading...';
}
return this.state.isOnline ? 'Online' : 'Offline';
}
}
componentDidMount
와 componentWillUnmount
이 두 메서드 내에 개념상 똑같은 effect
에 대한 코드가 있음에도 불구하고, 생명주기 메서드는 이를 분리하게 만든다.
Hook을 사용하는 경우
import React, { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
// effect 이후에 어떻게 정리(clean-up)할 것인지 표시합니다.
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
정리의 실행을 위한 별도의 effect
가 필요한 게 아닌, useEffect
는 이를 함께 다루도록 고안되었다. effect
가 함수를 반환하면 리액트는 그 함수를 정리가 필요한 때에 실행시킨다.
effect
에서 함수를 반환하는 이유는 effect
를 위한 추가적인 정리 메커니즘이다. 구독의 추가와 제거가 모두 하나의 effect
를 구성한다.
리액트가 effect
를 정리하는 정확한 시점은 컴포넌트가 마운트 해제되는 때에 정리를 실행한다. 단, effect
는 렌더링이 실행될 때마다 실행되기 때문에 effect
를 실행하기 전 이전의 렌더링에서 파생된 effect
를 정리하는 이유가 바로 이 때문이다. -> 버그를 방지하고, 성능 저하 문제가 발생할 경우 effect
를 건너뛸 수 있다.
➡️ effect Hook
은 정리가 필요한 경우에는 함수를 반환하고, 정리가 필요하지 않는 경우에는 그 어떤것도 반환하지 않는다. 이처럼 effect Hook
은 이 두 가지 경우를 한 개의 API로 통합한다.