useEffect
Hook을 사용하여 함수 컴포넌트에서 side effect를 수행할 수 있음render
메서드가 아니라 componentDidMount
와 componentDidUpdate
메서드에 side effect를 둠class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
componentDidMount() {
document.title = `You clicked ${this.state.count} times`;
}
componentDidUpdate() {
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>
);
}
}
useEffect
Hook을 사용하여 side effect를 수행함useEffect
의 인수로 받은 함수 effect를 기억했다가 DOM 업데이트를 수행한 이후에 불러냄useEffect
를 컴포넌트 내부에 둠으로써 effect를 통해서 state 변수와 props에 접근할 수 있음 (같은 함수 스코프 내에 있기 때문에 접근 가능함)useEffect
는 기본적으로 렌더링 이후(첫 렌더링과 모든 업데이트 이후)에 수행됨useEffect
에 전달되는 함수 effect는 모든 렌더링에서 다름import React, { 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>
);
}
useState
를 사용하여 'count' state 변수를 선언함useEffect
를 사용하여 React가 전달 받은 effect를 기억하게 함componentDidMount
에서 구독(subscription)을 설정하고, componentWillUnmount
에서 이를 clean-up함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에 대한 코드가 들어감useEffect
Hook을 사용하여 Clean-up을 이용한 side effect를 수행함useEffect
의 모든 effect에서 clean-up을 위한 함수를 반환할 수 있음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);
// clean-up 함수
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
componentDidMount
와 componentDidUpdate
와 달리, useEffect
에 전달된 effect는 지연된 이벤트 동안, layout과 paint 이후에 실행됨useLayoutEffect
Hook을 사용해야함useLayoutEffect
는 useEffect
와 동일한 signiture를 가지고 있으나 effect가 DOM 조작 후 동기적으로 실행됨flushSync
로 감싸진 업데이트의 결과는 layout과 paint 이전에 동기적으로 실행됨flushSync
의 호출자에 의해 관찰될 수 있음Note
- 위 경우
useEffect
에 전달된 effect가 실행되는 시점에 영향을 주지만, 이러한 effect들 안에서 예정된 업데이트들은 여전히 지연됨- 반면에
useLayoutEffect
에 전달된 effect는 실행되고 즉시 effect 안의 업데이트들이 처리됨
useEffect
가 브라우저가 paint될 때까지 지연되더라도, 새 렌더링 전에 실행됨function FriendStatusWithCounter(props) {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
// ...
}
useEffect
의 두 번째 인수 dependenciesuseEffect
는 두 번째 인수로 선택적으로 배열을 받을 수 있음[]
)을 전달할 경우, effect를 실행하고 clean-up하는 과정을 한 번만 실행함useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // count가 바뀔 때만 effect를 재실행함
useEffect
가 두 번 호출되어도 사용자가 리렌더링을 느끼지 못하게 하기 위해서, useEffct
의 effect 함수에서 clean-up 함수를 반환해야 함effect -> clean-up -> effect
순서로 실행되므로 하나의 effect만 실행된 것처럼 느껴짐useEffect
내부에서 setState
함수를 호출하면 clean-up 함수 작성과 무관하게 setState
를 두 번 호출하게 되므로, useEffect
내부에서 상태 업데이트를 피할 것function TodoList({ todos, filter }) {
const [newTodo, setNewTodo] = useState('');
const [visibleTodos, setVisibleTodos] = useState([]);
useEffect(() => {
setVisibleTodos(getFilteredTodos(todos, filter));
}, [todos, filter]);
// ...
}
useEffect
의 effect 내부에서 setVisibleTodos를 호출하는 것은 불필요한 리렌더링을 발생시킴function TodoList({ todos, filter }) {
const [newTodo, setNewTodo] = useState('');
const visibleTodos = getFilteredTodos(todos, filter);
// ...
}
import { useMemo, useState } from 'react';
function TodoList({ todos, filter }) {
const [newTodo, setNewTodo] = useState('');
const visibleTodos = useMemo(() => getFilteredTodos(todos, filter), [todos, filter]);
// ...
}
useMemo
를 사용함