๐ [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