๐ [React] React16๊ณผ Suspense, ์ง์ ๊ตฌํํด๋ณด๊ธฐ 1
๊ฐ์
Suspense๋ ์ด๋ป๊ฒ ๋์ํ ๊น?
ErrorBoundary๊ฐ error๋ฅผ ๊ฐ์งํด์ throw ํ๋ ๊ฒ ์ฒ๋ผ, Suspense๋ Promise๋ฅผ ๊ฐ์งํ๋ค.
Suspense ๋ ์ ์ฌ์ฉํ๋ฉด ์ข์๊น?
๋ฌด์๊ณผ ์ด๋ป๊ฒ๋ฅผ ๋ถ๋ฆฌํ ์ ์๊ณ , ์ปดํฌ๋ํธ ๋ด๋ถ์์๋ ๋ฐ์ดํฐ ํ์นญ์ ๊ด๋ จ๋ ์ํ(๋ก๋ฉ, ์คํจ, ์ฑ๊ณต)๋ฅผ ๊ด๋ฆฌํ๊ฑฐ๋ ์ ๊ฒฝ์ฐ์ง ์์๋ ๋๋ค.
โ ์ ์ธ์ ํ๋ก๊ทธ๋๋ฐ(์ด๋ค๊ฑธ ๊ฐ์ง๊ณ ๋ฌด์์ ํ ์ง)์ด ๊ฐ๋ฅํด์ง
๋์์ ํจ๊ณผ์ Suspense
- ๋์์ ํจ๊ณผ๋ ๊ฐ์ธ๊ณ ์๋ ํจ์์ ๋ก์ง์ด ๊ฐ์ธ์ง(๋ดํฌํ๋) ํจ์์ ์ญํ ์ ๋ถ๋ฆฌํ ๋ ์คํ๋๋ค.
- React์ Suspense๋ ์์ ์ปดํฌ๋ํธ๋ฅผ ๊ฐ์ธ๋ ๋ถ๋ชจ ์ปดํฌ๋ํธ์๊ฒ ๋ก๋ฉ UI ํ์๋ผ๋ ์ญํ ์ ๋ถ๋ฆฌํ๊ณ ์๋ค.
- React์ Suspense์ ์ฐฝ์ ์๋ฆฌ๋ ๋์์ ํจ๊ณผ์ด๋ค.
๋ณธ๋ก
React 18 ์ด์ ์ Suspense๋ Data Fetching์ ์ํ Pending Handler๊ฐ ์๋๋ผ, ๊ธฐ์กด์ ์ํฐํด ๋ฐฉ์์ผ๋ก ์ด๋ฃจ์ด์ ธ์๋ Render๋ฐฉ์์ ๊ฐ์ ํด์ฃผ๋ ์ญํ ์ด๋ค. https://sangminnn.tistory.com/76
ํ์ํ ๊ตฌ์กฐ
- promise๋ฅผ ๊ฐ์งํด์ ์ปดํฌ๋ํธ๋ฅผ suspended ์ํ๋ก ๋ง๋ค์ด ์ค ์ฅ์น (data fetching์ ์ํ suspense)
react-query์ suspense ์ต์ ์ฌ์ฉํ์ ๋์ ๊ฐ์ ๊ตฌ์กฐ๋ฅผ ์ง์ ๋ง๋ค์ด๋ณด๋ ค๊ณ ํจ- ์ด๋ฏธ์ง, dynamic import, fetching์ ๋ชจ๋ ์ง์
- ์ดํ api ํธ์ถํ ๋ ์์ ์ฅ์น๋ฅผ ํตํด ํธ์ถํด์ผ ํจ
- ์๋ฌ ๋ฐ์ด๋๋ฆฌ
โ @toss/async-boundary
์ฐธ๊ณ ๋ธ๋ก๊ทธ
- https://jbee.io/react/error-declarative-handling-1/
- https://maxkim-j.github.io/posts/suspense-argibraic-effect
- https://velog.io/@imnotmoon/React-Suspense-ErrorBoundary-์ง์ -๋ง๋ค๊ธฐ
WrappedPromise(createPromiseResource) โ useFetch ํ ์ผ๋ก ๊ณ ๋ํ
// suspense์ ํจ๊ป ์ฌ์ฉํ๊ธฐ ์ํด, ์์ ์ปดํฌ๋ํธ์์ ๋น๋๊ธฐ์ ์ผ๋ก ๋ฐ์ดํฐ ํจ์น ์ ์ด ์ ํธ ์ฌ์ฉ
const createPromiseResource = (promise) => {
let status = "pending";
let result = null;
const suspender = promise.then(
(res) => {
status = "fulfilled";
result = res;
},
(err) => {
status = "rejected";
result = err;
}
);
return {
read() {
switch (status) {
case "pending":
throw suspender;
case "fulfilled":
throw result;
case "rejected":
throw result;
default:
break;
}
},
};
};
export default createPromiseResource;
// ๋น๋๊ธฐ ํธ์ถ์ ํ๋ ์ปดํฌ๋ํธ
import React, { useState, useEffect } from "react";
import { getUser } from "../apis";
import createPromiseResource from "../utils/createPromiseResource";
// ์ฌ๊ธฐ์ react-query๋ axios๋ก ๋น๋๊ธฐ fetch ์์
์งํ
// https://6391fa92b750c8d178d35d54.mockapi.io/api/profile/:id
const useResource = (id) => createPromiseResource(getUser(id)).read();
function UserItem({ id }) {
const data = useResource(id);
return <div>์ด๋ฆ: {data.name}</div>;
}
export default UserItem;
ErrorBoundary
import React from "react";
class ErrorBoundary extends React.Component {
constructor(props) {
super(props); // props๋ก errorFallback์ ๋ฐ์ ์ ์์
this.state = {
error: null,
};
}
static getDerivedStateFromError(error) {
console.log("getDerivedStateFromError");
return { error };
}
componentDidCatch(err, info) {
console.log("componentDidCatch", err, info);
this.setState({
error: err,
});
}
render() {
if (this.state.error) {
return this.props.fallback ?? <h3>์๋ฌ๊ฐ ๋ฐ์ํ์ต๋๋ค :(</h3>;
}
return this.props.children;
}
}
export default ErrorBoundary;
1์ฐจ ์๋ ์ฝ๋์์ ๋ฌธ์ ์
- ๋น๋๊ธฐ ํธ์ถ์ ํ๋ ์ปดํฌ๋ํธ์์
createPromiseResource๋ฅผ ์ฌ์ฉํ ๋,-
์ฆ์ ์คํ ํจ์๋ก
getUser(id)๋ฐ๊ณ ์์๊ธฐ ๋๋ฌธ์ ๊ณ์ํด์ ์คํ๋๊ณ ์์์
-
- ๋ถ๋๋ฝ๊ฒ๋ ๊ทผ๋ณธ์ ์ผ๋ก
.then().catch()๋ฌธ์ ์๋ชป ์ฐ๊ณ ์์์(๋ฌธ๋ฒ ์๋ฌ) -
1์ ๋ฌธ์ ์ ์ ํด๊ฒฐํ๋ฉด์ ๊ทผ๋ณธ์ ์ธ ๋ฌธ์ ๋ฅผ ๋ฐ๊ฒฌํ๋๋ฐ,
createPromiseResource์ ํธ์ ๋ฆฌ์กํธ ๋ฐ์ ๋นผ๋๊ธด ํ์ง๋ง ์ฆ์ ์คํ ํจ์๋ก ๋ง๋ค์๊ธฐ ๋๋ฌธ์ (.read()) ๋ฆฌ์กํธ ๋ด๋ถ์์ ์คํํ ๋๋ง๋ค ์๋ก ์์ฑ๋๊ณ ์์์โ status๊ฐ pending์ธ ์ํ๊ฐ ๊ณ์ ์์ฑ (์๋๋ ํ ๋ฒ๋ง ๋ง๋ค์ด์ง๊ณ ๊ทธ status์ ์ํ๊ฐ ์ ๋ฐ์ดํธ ๋์ด์ผ ํจ)
ํด๊ฒฐ ๋ฐฉ๋ฒ
-
์๋์ ๊ฐ์ด ์ฆ์ ์คํ ํจ์๋ก ์ฌ์ฉํ ๋ถ๋ถ์ ์์ ํจ
// ๋น๋๊ธฐ ํธ์ถ์ ํ๋ ์ปดํฌ๋ํธ const useResource = (id) => createPromiseResource(() => getUser(id)); function UserItem({ id }) { const { data } = useResource(id).read(); // read()๋ ์์ ์์ผ๋ ์ฌ๊ธฐ ์์ผ๋ ์ฐจ์ด ์์ return <div>์ด๋ฆ: {data.name}</div>; } export default UserItem;์ด์ ๋ฐ๋ผ suspender์์ promise๋ฅผ ์คํํ ๋ค์ .then ํธ์ถํ๋๋ก ๋ณ๊ฒฝ
// createPromiseResource const suspender = promise().then(...) // ๊ธฐ์กด: promise.then(...) -
๋ฌธ๋ฒ ์์
// createPromiseResource let suspender = promise() .then((res) => { status = "fulfilled"; result = res; }) .catch((err) => { status = "rejected"; result = err; }); -
์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด์๋ ๋ฆฌ์กํธ ์๋ช ์ฃผ๊ธฐ๋ฅผ ๋ฒ์ด๋ ์ ์ญ ์ํ ๊ด๋ฆฌ๊ฐ ํ์ํ๋ค. (useRef ๋ฑ๋ ์์ฉ ์์ - suspense์ ๊ฐํ ์ํ์์๋ ์ ์ด์ ๋ ๋๋ง ์กฐ์ฐจ ํ์ง ์์ผ๋ฏ๋ก ๋น๊ตํ ๋์์ด ์์)
- store ์ ์ญ ๊ฐ์ฒด ์ถ๊ฐ
- ์์ฒญ์ ํ ๋๋ง๋ค ์ด๋ค ์์ฒญ์ ๋ํ ์ํ์ธ์ง ์๋ณํ๊ธฐ ์ํด key ์ถ๊ฐ (feat. react-query)
const store = {}; const createPromiseResource = (key, promise) => { if (!store[key]) { store[key] = { status: "pending", result: null, }; } let suspender = promise() .then((res) => { store[key].status = "fulfilled"; store[key].result = res; }) .catch((err) => { store[key].status = "rejected"; store[key].result = err; }); return { read() { switch (store[key].status) { case "pending": throw suspender; case "rejected": throw store[key].result; default: return store[key].result; } }, }; }; export default createPromiseResource;
๋ฆฌํฉํ ๋ง ํ๋ ๊ณผ์ ๊ณผ ์ป๊ฒ ๋ ๊ฒฐ๊ณผ๋ฌผ์ ๋ํ ์ ๋ฆฌ๋ ๋ค์ ํฌ์คํ ์์..!
# ์นดํ ๊ณ ๋ฆฌ
- BOJ 36
- Algorithm 12
- CodingTest 11
- Web 9
- Javascript 8
- Vue 7
- React 7
- DBProject 4
- Python 3
- Tech-interview 3
- Express 3
- Next 3
- Github 2
- Django 2
- C 1
- C++ 1
- WebGame 1