[React] 리μ‘νΈ μμ€νμ€?! (React Suspense)
μ΅κ·Ό λΈλ‘κ·Έ κΈμ μ½μ΄λ³΄λ€ 리μ‘νΈ μμ€νμ€λΌλ κ°λ μ μκ² λμλ€. μ΄μ κΉμ§ μ§ννλ νλ‘μ νΈ λ° μμΌλ‘ μ§νν νλ‘μ νΈμ μ μ©νλ©΄ μ’μ κ²μ΄λΌκ³ μκ°ν΄ μμλ³΄κ² λμλ€.
π§ React Suspense λ?
리μ‘νΈμ μλΈνμ€λ 16.6 λ²μ μμ μ€νμ (experimental) κΈ°λ₯μΌλ‘ μΆκ°λμκ³ 18λ²μ λΆν° 곡μμ μΌλ‘ μ μ κΈ°λ₯μΌλ‘ μ§μνκΈ° μμνλ€. μ΄ μμ€νμ€λ₯Ό μ΄μ©νλ©΄ μ΄λ€ μμ μ΄ μ’ λ£λκΈ° μ μ μ»΄ν¬λνΈμ λλλ§μ μ€λ¨μμΌ λμμ λλλ§μ΄ λκ² νλ€λμ§, λ€λ₯Έ μ»΄ν¬λνΈμ λλλ§μ μ μμλ‘ μ΄λ μ μλ€.
μλ₯Ό λ€μ΄ (λΆλͺ¨ μ»΄ν¬λνΈ - μμ μ»΄ν¬λνΈ1 - μμ μ»΄ν¬λνΈ2) μ²λΌ μ»΄ν¬λνΈλ€μ΄ μ‘΄μ¬ν λ λͺ¨λ μ»΄ν¬λνΈκ° λΉλκΈ° μμ μ ν¬ν¨νκ³ μλ€κ³ νλ€λ©΄ λΆλΆλͺ ν λΉλκΈ° λμμ μν μ’ λ£ μμ μΌλ‘ μΈν΄ λΆλΆμ μΌλ‘ μΌλΆμ© λλλ§λ μ μλ€. νμ§λ§ μμ€νμ€ κΈ°λ₯μ μ΄μ©νλ©΄ νλ²μ 보μ¬μ€ μ μλ€.
λ΄κ° μ§μ κ²½ννλ κ²μ μμλ‘ λ€μ΄λ³΄λ©΄ μ΄μ μ "λ¨Ήλμ₯" νλ‘μ νΈλ₯Ό μ§ννλ μ μ΄ μλ€.(https://mukdaejang-3b8e6.web.app/bestRestaurants/Seocho-Food) μ΄λ μ€μΌλ ν€ UIκ° λ±μ₯νκ³ λ°μ΄ν°κ° λ‘λλλ©΄ νλλμ© λ³΄μ¬μ€μΌ νλ μ 보λ€λ‘ λμ²΄κ° λλλ° μμκ° λ€μ£½λ°μ£½μ΄λ€. μ¬κΈ°μ μμ€νμ€ κΈ°λ₯μ μ¬μ©νλ€λ©΄ λμμ 보μ¬μ§λ©° UXλ₯Ό λμ± λμΌ μ μμ κ²μ΄λΌλ μκ°μ νλ€.
μμ€νμ€λ₯Ό μ΄μ©νλ©΄ λλλ§ μκΈ°λ₯Ό μ‘°μ ν μ μλ€.
μ»΄ν¬λνΈκ° μ€νλλ μμλ₯Ό μ‘°μ ν΄μ λλλ§ μμλ₯Ό μ‘°μ ν μ μλ κ²μ κΈ°λνμΌλ κ·Έλ°κ²μ μλμλ€.
μμ μ»΄ν¬λνΈμμ λΉλκΈ° μμ μ μννκ³ λ‘λλ λ°μ΄ν° λλ 리μμ€λ₯Ό νμ μ»΄ν¬λνΈλ‘ μ λ¬νλ€. μ΄λ μμ μ»΄ν¬λνΈμμ μμ§ λ‘λλμ§ μμ 리μμ€λ₯Ό νμν΄μΌ νλ κ²½μ° 'Suspense' ꡬμ±μμλ₯Ό μ¬μ©νμ¬ λ¦¬μμ€κ° λ‘λλλ λμ νμν λ체 μ»΄ν¬λνΈλ₯Ό μ§μ ν μ μλ€.
function App() {
return (
<div>
<Suspense fallback={<Loading />}>
<Data resource={getCatImg()} />
</Suspense>
</div>
);
}
fallbackμ΄λΌλ νλ‘νΌν°λ₯Ό μ¬μ©ν΄μ λ체ν μ»΄ν¬λνΈ νΉμ λ΄μ©μ 보μ¬μ€ μ μλ€. DataλΌλ μμ μ»΄ν¬λνΈμλ resourceλΌλ propμΌλ‘ getCatImgλΌλ ν¨μμ λ°νκ°μ΄ μ λ¬λλλ° getCatImg ν¨μμ λ΄μ©μ λ€μκ³Ό κ°λ€.
const getCatImg = () => {
let data = null;
const suspender = fetch('https://api.thecatapi.com/v1/images/search?limit=10')
.then(response => response.json())
.then(d => {
data = d;
});
return {
read() {
if (data === null) {
throw suspender;
} else {
return data;
}
},
};
};
getCatImg ν¨μλ κ³ μμ΄ μ¬μ§μ λν μ 보λ₯Ό λ΄κ³ μλ μ 보λ₯Ό fetch νλ€. μ΄ν νλ‘λ―Έμ€μ Pendingμ΄λ Fulfilled μ¬λΆμ μκ΄μμ΄ κ²½μ°μ λ°λΌ λ€λ₯Έ λμμ νλ readλΌλ λ©μλλ₯Ό κ°μ§ κ°μ²΄λ₯Ό λ°ννλ€.
function Data({ resource }) {
const data = resource.read();
return (
<section className="container">
<h2 className="title">Numbers Box</h2>
<ul>
{data.map((item, i) => (
<li key={i}>
<img className="img" src={item.url} alt="" />
</li>
))}
</ul>
</section>
);
}
Data μ»΄ν¬λνΈλ μμ κ°μ΄ ꡬμ±μ΄ λμ΄ μλλ° λΆλͺ¨μκ²μ λ°μ resourceμ read λ©μλλ₯Ό μ€νν΄ μνλ₯Ό νμΈν μ μλ€. μ΄λ λ§μ½ Fulfilled λμ΄ λ°μ΄ν°κ° μ μμ μΌλ‘ λ‘λλμλ€λ©΄ Data μ»΄ν¬λνΈκ° λλλ§λκ² μ§λ§ μμ§ pending μνλΌλ©΄ Suspenseμ fallback νλ‘νΌν°μ λ€μ΄κ° 리μ‘νΈ λ Έλλ‘ λ체λλ€. λ§μ½ μμ μ΄ μ€ν¨(Rejected)νλ€λ©΄ μ΄λ»κ² λ κΉ? μ΄λλ ErrorBoundaryλ₯Ό μ¬μ©νμ¬ μλ¬μνλ₯Ό ν¬μ°©νκ³ fallbackμ 리μ‘νΈ λ Έλλ‘ λ체ν μ μλ€.
function App() {
return (
<div>
<ErrorBoundary fallback={<Error />}>
<Suspense fallback={<Loading />}>
<Data resource={getCatImg()} />
</Suspense>
</ErrorBoundary>
</div>
);
}
리μ‘νΈμ κΈ°μ‘΄ λ°©μμ μ΄ν΄λ³΄λ©΄ νλμ μ»΄ν¬λνΈκ° μ»€μ§ μ μλ€. μμμ μ€λͺ ν μ½λμ κΈ°μ‘΄ μ½λλ₯Ό μ΄ν΄λ³΄λ©΄ λ€μκ³Ό κ°μ μ½λλ€μ΄ μ‘΄μ¬ν κ²μ΄λ€.
function App() {
const [isLoaded setIsLoaded] = useState(false);
const [data setData] = useState();
useEffect(() => {
const getCatImg = async () => {
const res = await fetch('https://api.thecatapi.com/v1/images/search?limit=9');
const data = await response.json();
return data;
}
setData(getCatImg());
setIsLoaded(true);
}, []);
return (
<div>
{ isLoaded ? <Data data={data} /> : <Loading /> }
</div>
);
}
μ΄λ λ‘λκ° λ¬λμ§λ₯Ό νμΈνλλ° λ§μ½ fetchνλ λ°μ΄ν°κ° μ¬λ¬ μ’ λ₯, λ§λ€λ©΄ μν©μ λ°λ₯Έ μμΈμ²λ¦¬λ₯Ό ν΄μ€μΌ νλ€. μ μ½λλ isLoadedλΌλ μνλ₯Ό ν΅ν΄ νλμ μμΈμ²λ¦¬λ₯Ό νμ§λ§ κ²½μ°μ μκ° λ§μμ§κ² λλ©΄ μΌνμ°μ°μλ₯Ό μ¬μ©ν κ²½μ° μ€νλ € κ°λ μ±μ΄ μ μ’μμ§κ²μ΄κ³ if λ¬Έμ ν΅ν λ§μ λΆκΈ°μ²λ¦¬κ° νμνκ² λ κ²μ΄λ€. λ°λ©΄ Suspenseμ κ²½μ° λ°μ΄ν°λ₯Ό λ‘λνκ³ κ·Έ κ²°κ³Όμ λ°λΌ λ체 λ Έλ λλ μ»΄ν¬λνΈλ₯Ό λλλ§ ν΄μ£ΌκΈ° λλ¬Έμ μμΈμ²λ¦¬κ° νΈνλ€. κ²°κ΅ μμ€νμ€λ₯Ό μ΄μ©νλ©΄ μ½λ κ°λ μ±κ³Ό μ μ§ λ³΄μμ±μ ν₯μμν¬ μ μμ κ²μΌλ‘ κΈ°λλλ€.