π useEffectμ κ³Όλν λ¨μ©: μλμΉ μμ μ¬μ€νκ³Ό 볡μ‘ν μμ‘΄μ± κ΄λ¦¬
Summary
React useEffect
ν
μ μ¬μ©ν λ, μ€μ λ‘μ§ μνμ νμ μλ μμ‘΄μ±μ μΆκ°νκ±°λ μ¬μ©μ μ‘μ
μ μν΄ νΈλ¦¬κ±°λμ΄μΌ ν λ‘μ§μ useEffect
μ λ¬Άμ΄λλ©΄ λΆνμν μ¬μ€ν, μ±λ₯ μ ν, μμΈ‘ λΆκ°λ₯ν λμμ μ λ°ν©λλ€. μ΅μνμ νμ μμ‘΄μ±λ§ ν¬ν¨νκ³ , μ΄λ²€νΈ κΈ°λ° λ‘μ§μ μ΄λ²€νΈ νΈλ€λ¬μμ μ§μ μ²λ¦¬νλ©°, useCallback
λ±μΌλ‘ ν¨μ μ°Έμ‘° μμ μ±μ ν보νμ¬ ν¨μ¨μ μΈ λ°μ΄ν° νλ¦μ λ§λ€μ΄μΌ ν©λλ€.
Why Wrong?
λ§μ κ°λ°μλ€μ΄ useEffect
λ₯Ό μ¬μ©νμ¬ λ°μ΄ν°λ₯Ό κ°μ Έμ¬ λ, μ€μ API μμ²μ μ§μ μ μΈ μν₯μ μ£Όμ§ μλ μνλ props
κΉμ§ μμ‘΄μ± λ°°μ΄μ ν¬ν¨μν€λ κ²½μ°κ° νν©λλ€. μ΄λ ν΄λΉ props
λ μνκ° λ³κ²½λ λλ§λ€ λΆνμνκ² λ°μ΄ν° μ¬μμ²μ΄ λ°μνμ¬ μ±λ₯ μ ν, λ€νΈμν¬ λ¦¬μμ€ λλΉ, κ·Έλ¦¬κ³ μλ²μ λΆνμν λΆνλ₯Ό μ΄λν©λλ€. μλ₯Ό λ€μ΄, μ¬μ©μ νλ‘νμ userId
μ λ°λΌ λ°μ΄ν°λ₯Ό κ°μ ΈμμΌ νλλ°, λ¨μν UI νμμ©μΌλ‘ μ¬μ©λλ profileType
κ°μ props
λ₯Ό useEffect
μ μμ‘΄μ± λ°°μ΄μ μΆκ°νλ κ²½μ°μ
λλ€.
λν, useEffect
λ μ£Όλ‘ μ»΄ν¬λνΈ μλͺ
μ£ΌκΈ° λ° μμ‘΄μ± λ³νμ λ°λΌ 'λ°μμ μΌλ‘' λκΈ°νλμ΄μΌ νλ λΆμ ν¨κ³Όλ₯Ό μ²λ¦¬νλ λ° μ ν©ν©λλ€. κ·Έλ¬λ μ¬μ©μ μΈν°λμ
(λ²νΌ ν΄λ¦, νΌ μ μΆ λ±)κ³Ό κ°μ΄ 'λͺ
μμ μΌλ‘ νΈλ¦¬κ±°λμ΄μΌ νλ' λ‘μ§κΉμ§ useEffect
λ΄λΆμ λ°°μΉνλ κ²½μ°κ° λ§μ΅λλ€. μ΄λ μ½λμ μλλ₯Ό λͺ¨νΈνκ² λ§λ€κ³ , νΉμ μμ μλ§ μ€νλμ΄μΌ ν λ‘μ§μ΄ μμΈ‘ λΆκ°λ₯ν μμ μ μ¬μ€νλ μνμ λμ
λλ€. κ²°κ³Όμ μΌλ‘ λλ²κΉ
μ μ΄λ ΅κ² νκ³ , μ¬μ©μκ° μ§μ λ°μ΄ν° κ°±μ μ μν λ 볡μ‘ν μν μ‘°μμ΄ νμνκ² λ©λλ€.
How to Fix?
useEffect
μ μμ‘΄μ± λ°°μ΄μλ ν΄λΉ ν¨κ³Ό(effect)κ° μ€νλμ΄μΌ νλ μ΅μνμ νμμ μΈ κ°λ€λ§ ν¬ν¨ν΄μΌ ν©λλ€. API νΈμΆμ κ²½μ°, μ€μ API μμ² νλΌλ―Έν°μ μ§μ μ μΈ μν₯μ λ―ΈμΉλ props
λ μν(μ: userId
)λ§μ μμ‘΄μ±μΌλ‘ ν¬ν¨νκ³ , UI νμμ©μΌλ‘λ§ μ¬μ©λλ props
(profileType
)λ μ μΈν΄μΌ ν©λλ€.
μ¬μ©μ μΈν°λμ
μ μν΄ νΈλ¦¬κ±°λλ λ‘μ§(μ: 'μλ‘κ³ μΉ¨' λ²νΌ ν΄λ¦)μ useEffect
λ΄λΆκ° μλ, ν΄λΉ μ΄λ²€νΈ νΈλ€λ¬ ν¨μ λ΄μμ μ§μ νΈμΆνλ κ²μ΄ λ°λμ§ν©λλ€. μ΄λ₯Ό μν΄ λ°μ΄ν° ν¨μΉ λ‘μ§μ useCallback
μΌλ‘ κ°μΈ ν¨μ μ체μ μ°Έμ‘° μμ μ±μ ν보νκ³ , νμμ λ°λΌ useEffect
(μ»΄ν¬λνΈ λ§μ΄νΈ μ λλ ν΅μ¬ μμ‘΄μ± λ³κ²½ μ)μ μ΄λ²€νΈ νΈλ€λ¬ λͺ¨λμμ ν΄λΉ ν¨μλ₯Ό μ¬μ¬μ©ν μ μλλ‘ ν©λλ€. useCallback
μ λΆνμν ν¨μ μ¬μμ±μ λ§μ useEffect
μ λΆνμν μ¬μ€νμ λ°©μ§νλ λ° λμμ μ€λλ€.
λν, μ»΄ν¬λνΈ μΈλ§μ΄νΈ μ λΉλκΈ° μμ²μ΄ μλ£λμ§ μμ λ°μνλ λ©λͺ¨λ¦¬ λμλ κ²½μ 쑰건(Race Condition)μ λ°©μ§νκΈ° μν΄ AbortController
λ₯Ό μ¬μ©νμ¬ useEffect
μ ν΄λ¦°μ
ν¨μμμ μμ²μ μ·¨μνλ ν¨ν΄μ κ³ λ €ν΄μΌ ν©λλ€. μ΄λ μ»΄ν¬λνΈμ μ±
μ λΆλ¦¬λ₯Ό λͺ
νν νκ³ , λΆνμν λ€νΈμν¬ μμ²μ μ€μ΄λ©°, μ½λμ κ°λ
μ±κ³Ό μ μ§λ³΄μμ±μ ν₯μμν΅λλ€.
Before Code (Bad)
import React, { useState, useEffect } from 'react';
function UserProfile({ userId, profileType }) {
const [userData, setUserData] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
// β μν°ν¨ν΄: profileTypeμ API νΈμΆμ μ§μ μ μΈ μν₯μ μ£Όμ§ μμ§λ§ μμ‘΄μ± λ°°μ΄μ ν¬ν¨λμ΄
// profileTypeμ΄ λ³κ²½λ λλ§λ€ λΆνμνκ² λ°μ΄ν° μ¬μμ²μ΄ λ°μν μ μμ.
// λν, λͺ
μμ μΈ 'μλ‘κ³ μΉ¨' κΈ°λ₯μ ꡬννκΈ° μ΄λ €μ.
useEffect(() => {
if (!userId) return;
const fetchUserData = async () => {
setIsLoading(true);
setError(null);
try {
// profileTypeμ΄ μ€μ λ‘λ API 쿼리μ μ¬μ©λμ§ μκ±°λ, UI νμμ©μ΄λΌλ©΄ μ΄ μμ²μ λΆνμ
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error('Failed to fetch user data');
}
const data = await response.json();
setUserData(data);
} catch (err) {
setError(err.message);
} finally {
setIsLoading(false);
}
};
fetchUserData();
}, [userId, profileType]); // profileTypeμ΄ λ³κ²½λ λλ§λ€ λΆνμν μ¬μ€ν λ°μ κ°λ₯
if (isLoading) return <div>λ‘λ© μ€...</div>;
if (error) return <div>μλ¬ λ°μ: {error}</div>;
if (!userData) return <div>μ¬μ©μ μ 보λ₯Ό λΆλ¬μ¬ μ μμ΅λλ€.</div>;
return (
<div>
<h2>{userData.name}</h2>
<p>μ΄λ©μΌ: {userData.email}</p>
<p>νλ‘ν νμ
: {profileType}</p> {/* profileTypeμ λ¨μν UI νμμ©μΌλ‘ κ°μ */}
<button onClick={() => console.log('μλ‘κ³ μΉ¨ κΈ°λ₯ μΆκ° νμ')}>λ°μ΄ν° μλ‘κ³ μΉ¨ (미ꡬν)</button>
</div>
);
}
export default UserProfile;
After Code (Good)
import React, { useState, useEffect, useCallback } from 'react';
function UserProfile({ userId, profileType }) {
const [userData, setUserData] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
// β
κ°μ : λ°μ΄ν° ν¨μΉ λ‘μ§μ λ³λμ ν¨μλ‘ λΆλ¦¬νκ³ useCallbackμΌλ‘ λννμ¬ ν¨μ μ°Έμ‘° μμ μ± ν보
// userId μΈ λ€λ₯Έ prop/stateλ API νΈμΆμ μ§μ μν₯μ μ£Όμ§ μλλ€λ©΄ μμ‘΄μ±μμ μ μΈ
const fetchDataById = useCallback(async (id) => {
if (!id) {
setUserData(null);
return;
}
setIsLoading(true);
setError(null);
// AbortControllerλ₯Ό μ¬μ©νμ¬ μ»΄ν¬λνΈ μΈλ§μ΄νΈ μ μμ² μ·¨μ (λ©λͺ¨λ¦¬ λμ λ° κ²½μ 쑰건 λ°©μ§)
const controller = new AbortController();
const signal = controller.signal;
try {
const response = await fetch(`/api/users/${id}`, { signal });
if (!response.ok) {
throw new Error('Failed to fetch user data');
}
const data = await response.json();
setUserData(data);
} catch (err) {
if (err.name === 'AbortError') {
console.log('Fetch aborted');
} else {
setError(err.message);
}
} finally {
setIsLoading(false);
}
return controller; // cleanupμ μν΄ controller λ°ν
}, []); // μ΄ ν¨μλ μΈλΆ μ€μ½νμ μνμ μμ‘΄νμ§ μμΌλ―λ‘ λΉ λ°°μ΄ (userIdλ μΈμλ‘ λ°μ)
// β
κ°μ : useEffectλ μ΄μ userIdκ° λ³κ²½λ λλ§ λ°μ΄ν°λ₯Ό λ‘λνκ³ , ν΄λ¦°μ
ν¨μλ₯Ό μ 곡
useEffect(() => {
const controller = fetchDataById(userId);
return () => {
// μ»΄ν¬λνΈ μΈλ§μ΄νΈ λλ userId λ³κ²½ μ μ΄μ μμ² μ·¨μ
if (controller) controller.abort();
};
}, [userId, fetchDataById]); // fetchDataByIdλ useCallbackμΌλ‘ μμ μ μ΄λ―λ‘ userId λ³κ²½ μμλ§ μ€ν
// β
κ°μ : μ¬μ©μκ° λͺ
μμ μΌλ‘ λ°μ΄ν°λ₯Ό λ€μ λ‘λνκ³ μΆμ λ νΈμΆν ν¨μ (μ΄λ²€νΈ νΈλ€λ¬)
const handleRefreshClick = () => {
fetchDataById(userId);
};
if (isLoading) return <div>λ‘λ© μ€...</div>;
if (error) return <div>μλ¬ λ°μ: {error}</div>;
if (!userData) return <div>μ¬μ©μ μ 보λ₯Ό λΆλ¬μ¬ μ μμ΅λλ€.</div>;
return (
<div>
<h2>{userData.name}</h2>
<p>μ΄λ©μΌ: {userData.email}</p>
<p>νλ‘ν νμ
: {profileType}</p> {/* profileTypeμ UI νμμ©μΌλ‘λ§ μ¬μ© */}
<button onClick={handleRefreshClick} disabled={isLoading}>λ°μ΄ν° μλ‘κ³ μΉ¨</button>
</div>
);
}
export default UserProfile;