useEffect is a React Hook that lets you synchronize a component with an external system.
React Docs에 쓰여 있는 useEffect
의 정의다.
useEffect
는 컴포넌트를 외부 시스템과 동기화하기 위해 사용하는 hook이라고 한다.
React Docs - You Might Not Need An Effect에 따르면, 불필요하게 useEffect
를 사용하는 상황은 크게 두 가지가 있다.
1. 데이터 변경 (e.g. state)
2. 이벤트 핸들링 (e.g. API request)
useEffect
를 사용하지 말아야 할 곳에서 사용하는 실수들과 개선 방법을 알아볼 것이다.
To-do list 예제이다.
function TodoList({ todos, filter }) {
const [newTodo, setNewTodo] = useState('');
const [visibleTodos, setVisibleTodos] = useState([]);
useEffect(() => {
setVisibleTodos(getFilteredTodos(todos, filter));
}, [todos, filter]);
// ...
}
위와 같이 visibleTodos
라는 state를 만들어서 useEffect
를 통해 todo
나 filter
가 바뀔 때마다 변경하는 것은 불필요하고 비효율적이다. todos
나 filter
가 바뀌면 1차로 렌더링이 일어나고, visibleTodos가 바뀌면서 렌더링이 또 일어나게 된다.
다시 말해, 가지고 있는 props
나 state
를 사용해 만들 수 있는 값이면, 또 다른 state를 만들 필요가 없다는 것이다.
function TodoList({ todos, filter }) {
const [newTodo, setNewTodo] = useState('');
const visibleTodos = getFilteredTodos(todos, filter);
// ...
}
이런 식으로 코드를 바꾸면 코드가 간결해질 뿐만 아니라 성능도 개선된다.
만약 getFilteredTodos()
함수가 오래 걸리는 함수라면 useMemo
를 통해 추가적인 개선이 가능하다.
import { useMemo, useState } from 'react';
function TodoList({ todos, filter }) {
const [newTodo, setNewTodo] = useState('');
const visibleTodos = useMemo(() => getFilteredTodos(todos, filter), [todos, filter]);
// ...
}
로그인 Form 예제이다.
function Form() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [isSubmitting, setSubmitting] = useState(false);
const [userInfo, setUserInfo] = useState(null);
useEffect(() => {
(async () => {
if (!isSubmitting || !email || !password) return;
const user = await loginAPI({email, password});
setUserInfo(user);
setSubmitting(false);
})();
}, [isSubmitting]);
return (
<Form>
// ...
<SubmitButton
type="submit"
onClick={() => setSubmitting(true)}
>
{isSubmitting ? "Loading..." : "Sign In"}
</SubmitButton>
<Form>
);
}
버튼 클릭 이벤트가 isSubmitting
을 true로 만들고, 그로 인해 useEffect
가 호출되어 로그인 API를 호출하는 코드이다. 이 경우에는 useEffect
내부에 위치한 로그인 관련 로직이 이벤트와 직접적으로 연관된 로직이기 때문에 버튼 클릭 이벤트 핸들러 자체에서 실행되어야 한다.
// useLogin.js
export function useLogin() {
const [isLoading, setLoading] = useState(false);
const login = async (email, password) => {
if (!email || !password) return;
setLoading(true)
const user = await loginAPI({email, password});
setLoading(false);
return user
};
return [login, isLoading];
}
// Form.jsx
import { useLogin } from './useLogin';
function Form() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [login, isSubmitting] = useLogin();
return (
<Form>
// ...
<SubmitButton
type="submit"
onClick={() => login(email, password)}
>
{isSubmitting ? "Loading..." : "Sign In"}
</SubmitButton>
<Form>
);
}
로직을 어디에 위치시킬 지 판단하는 방법은 간단하다. 로직이 특정 인터랙션으로 인해 발생한다면 이벤트 핸들러에 넣어야 하고, 유저가 스크린에서 컴포넌트를 봄으로 인해 발생한다면 useEffect
에 위치해야 한다.