๋ฐ˜์‘ํ˜•

React v18 ๋ฒ„์ „๋ถ€ํ„ฐ Suspense๋ฅผ ๊ณต์‹์ ์œผ๋กœ ์ง€์›ํ•œ๋‹ค. Suspense๋Š” ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง์— ํ•„์š”ํ•œ ํŠน์ • ์ž‘์—…์ด ์™„๋ฃŒ๋˜์ง€ ์•Š์•˜์Œ์„ React์—๊ฒŒ ์•Œ๋ ค์ฃผ๋Š” ๋งค์ปค๋‹ˆ์ฆ˜์ด๋‹ค. ์ด ํŠน์ • ์ž‘์—…์€ ์—ฌ๋Ÿฌ๊ฐ€์ง€๊ฐ€ ์žˆ๊ฒ ์ง€๋งŒ Data Fetching ๊ฐ™์€ ๋น„๋™๊ธฐ ์ž‘์—…์ธ ๊ฒฝ์šฐ๊ฐ€ ๊ฐ€์žฅ ๋งŽ๋‹ค. Suspense๋ฅผ ์ด์šฉํ•˜๋ฉด, ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค ๋ถˆ๋Ÿฌ์˜ค์ง€ ๋ชปํ•œ ์ปดํฌ๋„ŒํŠธ์˜ ๋ Œ๋”๋ง์„ ์ž ์‹œ ์ค‘๋‹จ์‹œํ‚ค๊ณ  Loading ํ™”๋ฉด ๊ฐ™์€ ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋จผ์ € ๋ณด์—ฌ์ฃผ๋„๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๐Ÿ’ก Suspense๋Š” React Query, SWR ๊ฐ™์€ Data Fetching ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค๊ณ„๋๋‹ค. Data Fetching ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„  ์˜ต์…˜ ์ธ์ž๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ Suspense ๊ธฐ๋Šฅ์„ ํ™œ์„ฑํ™” ํ•  ์ˆ˜ ์žˆ๋‹ค. ์˜ˆ๋ฅผ๋“ค์–ด useSWR์—์„  ์„ธ๋ฒˆ์งธ ์ธ์ž์— { suspense: true }๋ฅผ ๋„˜๊ธฐ๋ฉด ๋œ๋‹ค.

 

Suspense ์žฅ์ 


Suspense๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์›Œํ„ฐํด(Waterfall), ๊ฒฝ์Ÿ ์ƒํƒœ(Race Condition)๋ฅผ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์›Œํ„ฐํด(Warterfall) ๋ฌธ์ œ ํ•ด๊ฒฐ

Data Fetching์—์„œ ์›Œํ„ฐํด ํ˜„์ƒ์€ ์ด์ „ Fetch ์š”์ฒญ์— ๋Œ€ํ•œ ์‘๋‹ต์ด ๋„์ฐฉํ•ด์•ผ ๋‹ค์Œ Fetch ์š”์ฒญ์„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋Š” ์ƒํ™ฉ์„ ๋งํ•œ๋‹ค. User ์ปดํฌ๋„ŒํŠธ์—์„œ ์ด๋ฆ„, ์ฃผ์†Œ, ์ด๋ฉ”์ผ ๊ฐ™์€ ์œ ์ € ์ •๋ณด๋ฅผ ๋ฐ›์•„์˜จ ํ›„ ๋ Œ๋”๋งํ•˜๊ณ , Posts ์ปดํฌ๋„ŒํŠธ์—์„  ํ•ด๋‹น ์œ ์ €(ID)์— ๋Œ€ํ•œ ํฌ์ŠคํŒ… ์ •๋ณด๋ฅผ ์š”์ฒญํ•˜๋Š” ์ƒํ™ฉ์„ ์˜ˆ๋กœ๋“ค ์ˆ˜ ์žˆ๋‹ค.

function User({ userId }) {
  const [loading, setLoading] = useState(false);
  const [user, setUser] = useState([]);

  useEffect(() => {
    // ...Fetch user data, update user state
  }, []);

  if (loading) return <p>Loading user...</p>;

  return (
    <div>
      <p>{user.name} ๋‹˜์ด ์ž‘์„ฑํ•œ ๊ธ€</p>
      <Posts userId={userId} />
    </div>
  );
}

function Posts({ userId }) {
  const [loading, setLoading] = useState(false);
  const [posts, setPosts] = useState([]);

  useEffect(() => {
    // ...Fetch posts data, update posts state
  }, []);

  if (loading) return <p>Loading posts...</p>;

  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

 

  1. (User ์ปดํฌ๋„ŒํŠธ) ์œ ์ € ๋ฐ์ดํ„ฐ ์š”์ฒญ → ์š”์ฒญ์ค‘์—” ๋กœ๋”ฉ ํ™”๋ฉด ํ‘œ์‹œ — 2์ดˆ ์†Œ์š”
  2. (User ์ปดํฌ๋„ŒํŠธ) ๋ฐ์ดํ„ฐ ์‘๋‹ต → ์œ ์ € ์ •๋ณด ๋ Œ๋” → Posts ์ปดํฌ๋„ŒํŠธ๋กœ userId ์ „๋‹ฌ
  3. (Posts ์ปดํฌ๋„ŒํŠธ) userId์— ๋Œ€ํ•œ ํฌ์ŠคํŒ… ๋ฐ์ดํ„ฐ ์š”์ฒญ → ์š”์ฒญ์ค‘์—” ๋กœ๋”ฉ ํ™”๋ฉด ํ‘œ์‹œ — 3์ดˆ ์†Œ์š”
  4. (Posts ์ปดํฌ๋„ŒํŠธ) ๋ฐ์ดํ„ฐ ์‘๋‹ต → ํฌ์ŠคํŒ… ๋ชฉ๋ก ๋ Œ๋”

 

User ์ปดํฌ๋„ŒํŠธ์—์„œ ์œ ์ € ๋ฐ์ดํ„ฐ๋ฅผ ์ •์ƒ์ ์œผ๋กœ ๋ฐ›์•„์™€์•ผ๋งŒ Posts ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ•ด๋‹น ์œ ์ €์— ๋Œ€ํ•œ ํฌ์ŠคํŒ… ์ •๋ณด๋ฅผ ์š”์ฒญํ•  ์ˆ˜ ์žˆ๋‹ค. ํฌ์ŠคํŒ… ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ค๋Š”๋ด 3์ดˆ๊ฐ€ ์†Œ์š”๋˜์ง€๋งŒ User ์ปดํฌ๋„ŒํŠธ์˜ ๋ฐ์ดํ„ฐ ์š”์ฒญ ๋•Œ๋ฌธ์— 2์ดˆ๋ฅผ ๋” ๊ธฐ๋‹ค๋ ค์•ผ ํ•˜๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. ์ด๋Ÿฐ ํ˜„์ƒ์„ ์›Œํ„ฐํด์ด๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค.

 

์ผ๋ฐ˜ Data Fetching์˜ ๋ Œ๋”๋ง ๊ณผ์ •

Data Fetching ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ปดํฌ๋„ŒํŠธ ํŠธ๋ฆฌ ๊ตฌ์กฐ์— ํ•„์š”ํ•œ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ๋ Œ๋”๋ง ์ด์ „์— ๋ฐ›์•„์™€์„œ ์›Œํ„ฐํด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ฌ๋•Œ๊นŒ์ง€ ํŠธ๋ฆฌ ๊ตฌ์กฐ์— ์žˆ๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋งํ•  ์ˆ˜ ์—†๋Š”, ๋˜ ๋‹ค๋ฅธ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

๋”๋ณด๊ธฐ
function fetchUser(userId) {
  return fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
}

function fetchPosts(userId) {
  return fetch(`https://jsonplaceholder.typicode.com/posts?userId=${userId}`);
}

// Data Fetching ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ผ๊ณ  ๊ฐ€์ •
function fetchData(userId) {
  return Promise.all([fetchUser(userId), fetchPosts(userId)])
    .then((res) => Promise.all(res.map((r) => r.json())))
    .then(([user, posts]) => ({ user, posts }));
}
// ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง ์‹œ์ž‘ ์ „ ๋ฐ์ดํ„ฐ ์š”์ฒญ
const promise = fetchData(1);

function User() {
  const [user, setUser] = useState(null);
  const [posts, setPosts] = useState(null);

  useEffect(() => {
    promise.then((data) => {
      setUser(data.user);
      setPosts(data.posts);
    });
  }, []);

  if (user === null) return <p>Loading user...</p>;

  return (
    <div>
      <p>{user.name} ๋‹˜์ด ์ž‘์„ฑํ•œ ๊ธ€</p>
      <Posts posts={posts} />
    </div>
  );
}

// ์ž์‹ ์ปดํฌ๋„ŒํŠธ์—์„  ๋”์ด์ƒ ๋ฐ์ดํ„ฐ ์š”์ฒญ์„ ํ•˜์ง€ ์•Š์Œ
function Posts({ posts }) {
  if (posts === null) <p>Loading posts...</p>;

  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

Data Fetching ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ ๋ Œ๋”๋ง ๊ณผ์ •

โญ๏ธ Suspense๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฐ์ดํ„ฐ ์š”์ฒญ ์งํ›„ <Suspense> ์ž์‹ ํŠธ๋ฆฌ์— ์žˆ๋Š” ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง์„ ์‹œ์ž‘ํ•œ๋‹ค. ๊ฐ ์ปดํฌ๋„ŒํŠธ์— ์š”์ฒญ ๋ฆฌ์†Œ์Šค๋ฅผ ์ฃผ์ž…ํ–ˆ๋‹ค๋ฉด, ์š”์ฒญ ๋ฆฌ์†Œ์Šค์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์„ ์ˆ˜ ์žˆ๋Š”์ง€ ์ง€์†์ ์œผ๋กœ ํ™•์ธํ•œ๋‹ค. ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์„ ๋• ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ์˜ ๋ Œ๋”๋ง์„ ์ž ์‹œ ๋ฉˆ์ถ˜ ํ›„ ํŠธ๋ฆฌ์— ์žˆ๋Š” ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ์˜ ๋ Œ๋”๋ง์„ ์‹œ๋„ํ•œ๋‹ค.

 

ํŠธ๋ฆฌ์— ๋ Œ๋”๋งํ•  ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋‚จ์•„์žˆ์ง€ ์•Š๊ณ  ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์–ด ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ •์ง€ ์ƒํƒœ๋ฉด, ๊ฐ€์žฅ ๋ฐ”๊นฅ Suspense(๊ฒฝ๊ณ„)์— ์žˆ๋Š” Fallback(๋กœ๋”ฉ UI ๋“ฑ)์„ ์ฐพ์•„ ๋ณด์—ฌ์ค€๋‹ค. ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์œผ๋ฉด ์ปดํฌ๋„ŒํŠธ์— ๋ฐ˜์˜ํ•˜๊ณ  Fallback์€ ํ•ด์ œ๋œ๋‹ค.

๋”๋ณด๊ธฐ
// ๊ณต์‹ ๋ฌธ์„œ ์ฐธ๊ณ ํ•ด์„œ ์ผ๋ถ€ ์ˆ˜์ •
// Suspense๋ฅผ ํ†ตํ•ฉํ•ด์„œ ๋งŒ๋“  ํŠน๋ณ„ํ•œ ๊ฐ์ฒด(Promise ์•„๋‹˜)
// { userId: 1, user: {read: ƒ}, posts: {read: ƒ} }
const resource = fetchData(1); // ๋ฐ์ดํ„ฐ ์š”์ฒญ ์‹œ์ž‘

function Main() {
  return (
    <Suspense fallback={<p>Loading user...</p>}>
      <User resource={resource} />
      <Suspense fallback={<p>Loading posts...</p>}>
        <Posts resource={resource} />
      </Suspense>
    </Suspense>
  );
}

function User({ resource }) {
  // read()๋ฅผ ์ง€์†์ ์œผ๋กœ ํ˜ธ์ถœํ•ด์„œ ๋ฐ์ดํ„ฐ๊ฐ€ ๋“ค์–ด์™”๋Š”์ง€ ํ™•์ธ
  // user ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์„ ์ˆ˜ ์—†๋‹ค๋ฉด ํ•ด๋‹น ๋ผ์ธ์—์„œ ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง ์ผ์‹œ ์ค‘์ง€
  const user = resource.user.read();
  return (
    <div>
      <p>{user.name} ๋‹˜์ด ์ž‘์„ฑํ•œ ๊ธ€</p>
    </div>
  );
}

function Posts({ resource }) {
  // read()๋ฅผ ์ง€์†์ ์œผ๋กœ ํ˜ธ์ถœํ•ด์„œ ๋ฐ์ดํ„ฐ๊ฐ€ ๋“ค์–ด์™”๋Š”์ง€ ํ™•์ธ
  // posts ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์„ ์ˆ˜ ์—†๋‹ค๋ฉด ํ•ด๋‹น ๋ผ์ธ์—์„œ ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง ์ผ์‹œ ์ค‘์ง€
  const posts = resource.posts.read();
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

์ด์ฒ˜๋Ÿผ ์ปดํฌ๋„ŒํŠธ ํŠธ๋ฆฌ์— ํ•„์š”ํ•œ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ์š”์ฒญํ•œ ํ›„ ๊ฐ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ Œ๋”๋ง์„ ์‹œ์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์›Œํ„ฐํด์ด ์—†๊ณ , ๋ฐ์ดํ„ฐ ์‘๋‹ต์„ ๋ฐ›๊ธฐ ์ „๊นŒ์ง€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋ง ํ•  ์ˆ˜ ์—†๋Š” ๋ฌธ์ œ๋„ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค.

 

Suspense๋ฅผ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ ๋ Œ๋”๋ง ๊ณผ์ •

 

๊ฒฝ์Ÿ ์ƒํƒœ(Race Condition) ๋ฐฉ์ง€

Suspense๋ฅผ ์ด์šฉํ•˜๋ฉด Data Fetching์— ์˜ํ•œ ๊ฒฝ์Ÿ ์ƒํƒœ(Race Condition)๋ฅผ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค. ์œ„ํ‚ค๋ฐฑ๊ณผ์— ๋”ฐ๋ฅด๋ฉด ๊ฒฝ์Ÿ ์ƒํƒœ๋Š” “ํŠน์ • ์ž์›์„ ์—ฌ๋Ÿฌ ํ”„๋กœ์„ธ์Šค๊ฐ€ ๋™์‹œ์— ์ ‘๊ทผํ•˜๋ ค๊ณ  ํ•  ๋•Œ ํƒ€์ด๋ฐ์ด๋‚˜ ์ˆœ์„œ ๋“ฑ์ด ๊ฒฐ๊ณผ์— ์˜ํ–ฅ์„ ์ค„ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ”์ด๋ผ๊ณ  ๋‚˜์™€์žˆ๋‹ค. ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์—์„œ ๊ฒฝ์Ÿ ์ƒํƒœ๋Š” ์—ฌ๋Ÿฌ๊ฐœ์˜ ๋น„๋™๊ธฐ ์ž‘์—…(Data Fetching ๋“ฑ) ๊ฒฐ๊ณผ๊ฐ€ ํ•˜๋‚˜์˜ DOM ๊ฐ์ฒด์— ๋ฐ˜์˜๋˜๋Š” ์ƒํ™ฉ์„ ์˜๋ฏธํ•œ๋‹ค.

 

๊ฒฝ์Ÿ ์ƒํƒœ ์˜ˆ์‹œ — ๋‹ค์ˆ˜์˜ ๋น„๋™๊ธฐ ์ž‘์—…(์œ ์ € ๋ฐ์ดํ„ฐ)์ด ํ•˜๋‚˜์˜ DOM(์œ ์ € ์ •๋ณด ์ปดํฌ๋„ŒํŠธ)์— ๋ฐ˜์˜ํ•˜๋Š” ์ƒํ™ฉ โ–ผ

 

์œ„ ์˜ˆ์ œ์—์„œ ๊ฐ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด Fake API ์š”์ฒญ์„ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜จ ํ›„ ํ•˜๋‚˜์˜ ๊ณตํ†ต ์ปดํฌ๋„ŒํŠธ(USER INFO)์— ๋ฐ˜์˜ํ•˜๊ณ  ์žˆ๋‹ค. ์—ฌ๋Ÿฌ ๋ฒ„ํŠผ์„ ์—ฐ์†ํ•ด์„œ ๋ˆŒ๋ €๋‹ค๋ฉด ์ผ๋ฐ˜์ ์œผ๋กœ ๋งˆ์ง€๋ง‰์— ๋ˆ„๋ฅธ ๋ฒ„ํŠผ์˜ ์ •๋ณด๊ฐ€ ๋ฐ˜์˜๋  ๊ฒƒ์ด๋ผ๊ณ  ๊ฐ€์ •ํ•œ๋‹ค. ํ•˜์ง€๋งŒ ๊ผญ ๊ทธ๋ ‡์ง€๋งŒ์€ ์•Š๋‹ค. AB ์ˆœ์„œ๋กœ ์š”์ฒญ์„ ๋ณด๋ƒˆ์ง€๋งŒ BA ์ˆœ์„œ๋กœ ์‘๋‹ต์ด ์˜ฌ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์ด์ฒ˜๋Ÿผ ๊ฒฝ์Ÿ ์ƒํƒœ๋Š” ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋˜๋Š” ์ˆœ์„œ๋ฅผ ์ž˜๋ชป ๊ฐ€์ •ํ•ด์„œ ๋ฐœ์ƒํ•˜๋Š” ๋ฒ„๊ทธ๋‹ค.

 

React์—์„  useEffect ์ข…์†์„ฑ ๋ฐฐ์—ด์˜ ๊ฐ’์ด ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค, ๋ฐ”๋€ ๊ฐ’์— ๋Œ€ํ•œ API ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ ๊ฒฝ์Ÿ ์ƒํƒœ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค(์ข…์†์„ฑ ๋ฐฐ์—ด ๊ฐ’์ด ๋‹จ์‹œ๊ฐ„๋‚ด ์—ฌ๋Ÿฌ๋ฒˆ ๋ณ€๊ฒฝ๋˜๋Š” ๊ฒฝ์šฐ). Axios์˜ ์ทจ์†Œ ํ† ํฐ(Cancel Token) ๋“ฑ์„ ์‚ฌ์šฉํ•ด ์ด์ „ ์š”์ฒญ์„ ์ทจ์†Œํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ์ง๊ด€์ ์ด์ง€ ์•Š๊ณ  ๋””๋ฒ„๊ทธํ•˜๊ธฐ๋„ ์‰ฝ์ง€ ์•Š๋‹ค.

// ์ž ์žฌ์ ์ธ ๊ฒฝ์Ÿ ์ƒํƒœ๋ฅผ ์œ ๋ฐœํ•˜๋Š” ์ฝ”๋“œ ์˜ˆ์‹œ
useEffect(() => {
  fetchUser(id).then((user) => setUser(user));
}, [id]);

 

์š”์ฒญ ๋ฆฌ์†Œ์Šค๋ฅผ State๋กœ ๊ด€๋ฆฌํ•˜๊ณ  Suspense๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฒฝ์Ÿ ์ƒํƒœ๋ฅผ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๐Ÿ’ก ref ๊ฐ’์„ ๋ณ€๊ฒฝํ•ด๋„ ์ปดํฌ๋„ŒํŠธ๋Š” ๋ฆฌ๋ Œ๋”๋ง๋˜์ง€ ์•Š๋Š”๋‹ค. ํ•˜์ง€๋งŒ changeId ํ•จ์ˆ˜์—์„œ ref ๊ฐ’์„ ๋ณ€๊ฒฝํ•  ๋•Œ resource ์ƒํƒœ๋„ ์—…๋ฐ์ดํŠธํ•˜๋ฏ€๋กœ ํ•ญ์ƒ ๋ณ€๊ฒฝ๋œ ref ๊ฐ’์„ ๋ Œ๋”๋ง ํ•œ๋‹ค. ์ฐธ๊ณ ๋กœ ๋ณ€์ˆ˜++ ํ›„์œ„ ์ฆ๊ฐ ์—ฐ์‚ฐ์ž๋Š” ์ฆ๊ฐ๋˜๊ธฐ ์ด์ „์˜ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค(fetchData ํŒŒ๋ผ๋ฏธํ„ฐ์— ์ฆ๊ฐ๋˜๊ธฐ ์ „ ๊ฐ’์ด ์ „๋‹ฌ๋จ).

// Suspense๋ฅผ ์ด์šฉํ•ด ๊ฒฝ์Ÿ ์ƒํƒœ๋ฅผ ํ•ด๊ฒฐํ•œ ์˜ˆ์‹œ (๊ณต์‹ ๋ฌธ์„œ ์ฐธ๊ณ ํ•ด์„œ ์ผ๋ถ€ ์ฝ”๋“œ ์ˆ˜์ •)
// { userId: 1, user: {read: ƒ}, posts: {read: ƒ} }
const initialResource = fetchData(1); // ๋ฐ์ดํ„ฐ ์š”์ฒญ ์‹œ์ž‘

function Main() {
  const [resource, setResource] = useState(initialResource);
  const nextId = useRef(resource.userId + 1);

  const changeId = useCallback(() => {
    setResource(fetchData(nextId.current++)); // ref ๊ฐ’ ๋ณ€๊ฒฝ → data ์š”์ฒญ → state ์—…๋ฐ์ดํŠธ
  }, []);

  return (
    <>
      <button onClick={changeId}>Next User</button>
      <Suspense fallback={<h1>Loading user...</h1>}>
        <User resource={resource} />
      </Suspense>
    </>
  );
}

function User({ resource }) {
  // read()๋ฅผ ์ง€์†์ ์œผ๋กœ ํ˜ธ์ถœํ•ด์„œ ๋ฐ์ดํ„ฐ๊ฐ€ ๋“ค์–ด์™”๋Š”์ง€ ํ™•์ธ
  // user ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์„ ์ˆ˜ ์—†๋‹ค๋ฉด ํ•ด๋‹น ๋ผ์ธ์—์„œ ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง ์ผ์‹œ์ •์ง€
  const user = resource.user.read();
  return <h1>{user.name}</h1>;
}
๋”๋ณด๊ธฐ

fetchData ํ•จ์ˆ˜๋Š” User, Posts ์ปดํฌ๋„ŒํŠธ์—์„œ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณตํ•˜๋Š” user, posts ์†์„ฑ์„ ํฌํ•จํ•œ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ๊ฐ ์†์„ฑ์— ์žˆ๋Š” read() ํ•จ์ˆ˜๋Š” ๋ฐ์ดํ„ฐ ์š”์ฒญ์ค‘์ผ ๋• suspense ๋ณ€์ˆ˜(API ํ˜ธ์ถœ ํ”„๋กœ๋ฏธ์Šค)๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ณ , ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์‹ ํ•˜๋ฉด ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ์‹ค์ œ ๊ตฌํ˜„์€ ๋” ๋ณต์žกํ•˜๊ฒ ์ง€๋งŒ Relay ๊ฐ™์€ ๋ฐ์ดํ„ฐ Fetching ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ตฌํ˜„์€ ์•„๋ž˜์™€ ๋น„์Šทํ•œ ๊ตฌ์กฐ๋กœ ๋˜์–ด ์žˆ๋‹ค๊ณ  ๋ณด๋ฉด ๋œ๋‹ค.

 

read() ํ•จ์ˆ˜๊ฐ€ ์ฝ์€ ๊ฒฐ๊ณผ ๊ฐ’์— ๋”ฐ๋ผ ์–ด๋–ค UI๋ฅผ ๋ณด์—ฌ์ค„์ง€ ๊ฒฐ์ •ํ•˜๋Š” ๊ตฌ์กฐ — ์ž‘๋™ ์›๋ฆฌ๋Š” ๋งํฌ ์ฐธ๊ณ 

  • throw suspender : read() ํ•จ์ˆ˜ ํ˜ธ์ถœ ๊ณ„์† / <Suspense>์˜ Fallback UI ํ‘œ์‹œ
  • (์—๋Ÿฌ) throw result : read() ํ•จ์ˆ˜ ํ˜ธ์ถœ ์ •์ง€ / <ErrorBoundary>์˜ Fallback UI ํ‘œ์‹œ
  • (์„ฑ๊ณต) throw result : read() ํ•จ์ˆ˜ ํ˜ธ์ถœ ์ •์ง€ / ์ปดํฌ๋„ŒํŠธ์— ์‘๋‹ต ๋ฐ์ดํ„ฐ ๋ฐ˜์˜

 

import axios from 'axios';

// ๊ณต์‹ ๋ฌธ์„œ ์ฐธ๊ณ ํ•ด์„œ ์ผ๋ถ€ ์ฝ”๋“œ ์ˆ˜์ •
export default function fetchData(userId) {
  const userPromise = fetchUser(userId); // ํ”„๋กœ๋ฏธ์Šค ๋ฆฌํ„ด
  const postsPromise = fetchPosts(userId); // ํ”„๋กœ๋ฏธ์Šค ๋ฆฌํ„ด
  return {
    userId,
    user: wrapPromise(userPromise),
    posts: wrapPromise(postsPromise),
  }; // { userId: 1, user: {read: ƒ}, posts: {read: ƒ} }
}

// Suspense integrations like Relay implement
// a contract like this to integrate with React.
// Real implementations can be significantly more complex.
// Don't copy-paste this into your project!
function wrapPromise(promise) {
  let status = 'pending';
  let result;
  const suspender = promise.then(
    (r) => {
      status = 'success';
      result = r.data;
    },
    (e) => {
      status = 'error';
      result = e;
    },
  );
  return {
    // ํ•จ์ˆ˜ ๋ฐ–์—์„œ ํ”„๋กœ๋ฏธ์Šค(suspender)์˜ ์ง„ํ–‰ ์ƒํ™ฉ์„ ์กฐํšŒํ•˜๋Š” ์ธํ„ฐํŽ˜์ด์Šค ์—ญํ• (ํด๋กœ์ € ํ™œ์šฉ)
    read() {
      if (status === 'pending') {
        throw suspender; // read() ํ˜ธ์ถœ ๊ณ„์† / <Suspense>์˜ Fallback UI ํ‘œ์‹œ
      } else if (status === 'error') {
        throw result; // read() ํ˜ธ์ถœ ์ •์ง€ / <ErrorBoundary>์˜ Fallback UI ํ‘œ์‹œ
      } else if (status === 'success') {
        return result; // read() ํ˜ธ์ถœ ์ •์ง€ / ์ปดํฌ๋„ŒํŠธ์— ์‘๋‹ต ๋ฐ์ดํ„ฐ ๋ฐ˜์˜
      }
    },
  };
}

const instance = axios.create({
  baseURL: 'https://jsonplaceholder.typicode.com',
});

function fetchUser(userId) {
  return instance({ url: `/users/${userId}` });
}

function fetchPosts(userId) {
  return instance({ url: `/posts?userId=${userId}` });
}

๋ฒ„ํŠผ์„ ํด๋ฆญํ•  ๋•Œ๋งˆ๋‹ค ์ƒˆ๋กœ์šด userId์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์š”์ฒญํ•˜๋Š”๋ฐ, ์š”์ฒญ์„ ๋ณด๋ƒ„๊ณผ ๋™์‹œ์— resource ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๊ฒƒ์— ์ฃผ๋ชฉํ•˜์ž. ์—…๋ฐ์ดํŠธ๋œ resource๋Š” <Suspense> ์ž์‹ ํŠธ๋ฆฌ์— ์žˆ๋Š” ์ปดํฌ๋„ŒํŠธ์— ์ฃผ์ž…๋˜๊ณ  resource๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ํ™•๋ณดํ•˜๋Š” ์ฆ‰์‹œ ์ปดํฌ๋„ŒํŠธ์— ๋ฐ˜์˜๋œ๋‹ค.

 

๊ฒฝ์Ÿ ์ƒํƒœ๊ฐ€ ์กด์žฌํ•œ ์ฝ”๋“œ์—์„  ์š”์ฒญ์‘๋‹ต์ƒํƒœ ๋ณ€๊ฒฝ ์ˆœ์„œ๋กœ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ ์ ˆํ•œ ์‹œ์ ์— State๋ฅผ ์„ค์ •ํ•˜์ง€ ์•Š์œผ๋ฉด ์ฝ”๋“œ๊ฐ€ ์ œ๋Œ€๋กœ ์ž‘๋™ํ•˜์ง€ ์•Š๋Š”๋‹ค. ๋ฐ˜๋ฉด Suspense๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์š”์ฒญ์ƒํƒœ ๋ณ€๊ฒฝ์‘๋‹ต ์ˆœ์„œ๋กœ ๋™์ž‘ํ•˜๋ฏ€๋กœ ์ƒํƒœ ๋ณ€๊ฒฝ ์‹œ์ ์„ ๊ณ ๋ ค๋ฅผ ํ•˜์ง€ ์•Š์•„๋„ ๋˜๊ณ  ๊ฒฝ์Ÿ ์ƒํƒœ๋„ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค.

 

Suspense ์˜ˆ์‹œ

User, Posts ์ปดํฌ๋„ŒํŠธ๋ฅผ Suspense๋กœ ๊ฐ์‹ธ๊ณ  ๊ฐ ์ปดํฌ๋„ŒํŠธ์—” ์š”์ฒญ resource(fetchData ํ•จ์ˆ˜๊ฐ€ ๋ฐ˜ํ™˜ํ•œ ๊ฐ์ฒด; ํ•จ์ˆ˜ ํ˜ธ์ถœ ์‹œ ๋ฐ์ดํ„ฐ ์š”์ฒญ ์‹œ์ž‘)๋ฅผ ๋„˜๊ธด๋‹ค. ๋ฐ์ดํ„ฐ๋ฅผ ์š”์ฒญ์ค‘์ผ ๋• Suspense์˜ Fallback์„ ํ‘œ์‹œํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์œผ๋ฉด Fallback์„ ํ•ด์ œํ•œ๋‹ค. if (...) ๊ตฌ๋ฌธ ํ˜น์€ ์—์„œ ์‚ผํ•ญ ์—ฐ์‚ฐ์ž๋ฅผ ์ด์šฉํ•ด ๋กœ๋”ฉ UI๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ์กฐ๊ฑด์„ ์ž‘์„ฑํ•˜์ง€ ์•Š์•„๋„ ๋˜๊ธฐ ๋•Œ๋ฌธ์— React์™€ ๋” ์ž˜ ์–ด์šธ๋ฆฌ๋Š” “์„ ์–ธ์ "์ธ ์ฝ”๋“œ๊ฐ€ ๋œ๋‹ค.

 

๐Ÿ’ก Suspense๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฐ์‹ธ๋ฉด ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ์— ๊ณ ์œ ํ•œ ๊ฒฝ๊ณ„๊ฐ€ ์ƒ๊ธด๋‹ค. Posts๋ณด๋‹ค User ๋ฐ์ดํ„ฐ๋ฅผ ๋จผ์ € ์‘๋‹ต ๋ฐ›์•˜๋‹ค๋ฉด User ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฐ์‹ธ๊ณ  ์žˆ๋Š” ๋ฐ”๊นฅ Suspense ๊ฒฝ๊ณ„๋Š” ํ•ด์ œ๋˜๊ณ  User ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋งํ•œ๋‹ค. User์™€ Posts ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋™์‹œ์— ๋ณด์—ฌ์ฃผ๊ณ  ์‹ถ์œผ๋ฉด ๋‘˜ ์‚ฌ์ด์˜ Suspense ๊ฒฝ๊ณ„๋ฅผ ์ œ๊ฑฐํ•˜๋ฉด ๋œ๋‹ค(Posts๋ฅผ ๊ฐ์‹ธ๊ณ  ์žˆ๋Š” Suspense ์ œ๊ฑฐ).

import React, { Suspense, useCallback, useState } from 'react';
import fetchData from './fetchData';

export default function Main() {
  const [id, setId] = useState(1);
  const resource = fetchData(id); // { userId: 1, user: {read: ƒ}, posts: {read: ƒ} }

  const changeId = useCallback(() => {
    setId((prev) => prev + 1);
  }, []);

  return (
    <div>
      <Suspense fallback={<Loading type="user" />}>
        <User resource={resource} />
        <Suspense fallback={<Loading type="posts" />}>
          <Posts resource={resource} />
        </Suspense>
      </Suspense>
      <button type="button" onClick={changeId}>
        {`Next ID (${id + 1})`}
      </button>
    </div>
  );
}

 

์š”์ฒญ resource ๊ฐ€ ์ฃผ์ž…๋œ ์ปดํฌ๋„ŒํŠธ์—์„  read() ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ์ฃผ๊ธฐ์ ์œผ๋กœ ์‘๋‹ต ๋ฐ์ดํ„ฐ๊ฐ€ ๋“ค์–ด์™”๋Š”์ง€ ํ™•์ธํ•œ๋‹ค(read ํ•จ์ˆ˜ ๋‚ด๋ถ€์— ์ฝ˜์†”์„ ์ฐ์–ด๋ณด๋ฉด ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›๊ธฐ์ „๊นŒ์ง€ ์ˆ˜์—†์ด ํ˜ธ์ถœ๋˜๋Š”๊ฑธ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค). ์•„์ง ๋ฐ์ดํ„ฐ ์‘๋‹ต์„ ๋ฐ›์ง€ ์•Š์•˜์œผ๋ฉด ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง์„ ์ผ์‹œ ์ •์ง€ํ•˜๊ณ , Suspense ํŠธ๋ฆฌ์— ์žˆ๋Š” ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ์˜ ๋ Œ๋”๋ง์„ ์‹œ๋„ํ•œ๋‹ค.

 

ํŠธ๋ฆฌ์— ๋ Œ๋”๋งํ•  ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋‚จ์•„์žˆ์ง€ ์•Š๊ณ  ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์–ด ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ •์ง€ ์ƒํƒœ๋ฉด, ๊ฐ€์žฅ ๋ฐ”๊นฅ Suspense(๊ฒฝ๊ณ„)์— ์žˆ๋Š” Fallback(๋กœ๋”ฉ UI ๋“ฑ)์„ ์ฐพ์•„ ๋ณด์—ฌ์ค€๋‹ค. ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์œผ๋ฉด ์ปดํฌ๋„ŒํŠธ์— ๋ฐ˜์˜ํ•˜๊ณ  Fallback์€ ํ•ด์ œ๋œ๋‹ค. 

 

๐Ÿ’ก read() ํ•จ์ˆ˜ ํ˜ธ์ถœ์€ API ์š”์ฒญ์ด ์•„๋‹Œ, ์‘๋‹ต ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๋Š” ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ์  ์ฐธ๊ณ . ๊ฒฐ๊ณผ์ ์œผ๋กœ read() ํ•จ์ˆ˜ ํ˜ธ์ถœ์€ ์‘๋‹ต ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์–ด์˜ค๊ฑฐ๋‚˜, ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ •์ง€์‹œํ‚จ๋‹ค.

function User({ resource }) {
  // read()๋ฅผ ์ง€์†์ ์œผ๋กœ ํ˜ธ์ถœํ•ด์„œ ๋ฐ์ดํ„ฐ๊ฐ€ ๋“ค์–ด์™”๋Š”์ง€ ํ™•์ธ
  // user ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์„ ์ˆ˜ ์—†๋‹ค๋ฉด ํ•ด๋‹น ๋ผ์ธ์—์„œ ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง ์ผ์‹œ์ •์ง€
  const user = resource.user.read();
  return <h1>{`Written by ${user.name} (${user.id})`}</h1>;
}

function Posts({ resource }) {
  // read()๋ฅผ ์ง€์†์ ์œผ๋กœ ํ˜ธ์ถœํ•ด์„œ ๋ฐ์ดํ„ฐ๊ฐ€ ๋“ค์–ด์™”๋Š”์ง€ ํ™•์ธ
  // posts ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์„ ์ˆ˜ ์—†๋‹ค๋ฉด ํ•ด๋‹น ๋ผ์ธ์—์„œ ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง ์ผ์‹œ์ •์ง€
  const posts = resource.posts.read();
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>
          <h3>{post.title}</h3>
          <p>{post.body}</p>
        </li>
      ))}
    </ul>
  );
}

 

์—๋Ÿฌ ์ฒ˜๋ฆฌ


๐Ÿ’ก ErrorBoundary๋Š” ์ž์‹ ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ฐœ์ƒํ•œ ์—๋Ÿฌ๋งŒ ๊ฒ€์ถœํ•  ์ˆ˜ ์žˆ๋‹ค. ์ž์‹ ์— ๋Œ€ํ•œ ์˜ค๋ฅ˜๋Š” ๊ฒ€์ถœํ•  ์ˆ˜ ์—†๋‹ค.

 

What are error boundaries in React?
Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of the component tree that crashed…
React ๊ณต์‹ ๋ฌธ์„œ

 

ํ”„๋กœ๋ฏธ์Šค์—์„œ .catch ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด ์—๋Ÿฌ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋“ฏ Suspense์—์„  ErrorBoundary(์—๋Ÿฌ ๊ฒฝ๊ณ„)๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์—๋Ÿฌ๋ฅผ ์ฒ˜๋ฆฌํ•œ๋‹ค. ErrorBoundary๋Š” ํด๋ž˜์Šคํ˜• ์ปดํฌ๋„ŒํŠธ์˜ ์ž์‹ ์ปดํฌ๋„ŒํŠธ์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ํ˜ธ์ถœ๋˜๋Š” getDerivedStateFromError, componentDidCatch ๋ผ์ดํ”„์‚ฌ์ดํด ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด ์—๋Ÿฌ๋ฅผ ๊ฒ€์ถœํ•œ๋‹ค. ํ•˜์ง€๋งŒ ํ•จ์ˆ˜ํ˜• ์ปดํฌ๋„ŒํŠธ์—” ๋Œ€์‘ํ•˜๋Š” ๋ฉ”์„œ๋“œ๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์— ํด๋ž˜์Šค ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ด์šฉํ•ด ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค.

 

getDerivedStateFromError ๋ฉ”์„œ๋“œ ํŠน์ง• | ์ฐธ๊ณ 

  • ์ž์‹ ์ปดํฌ๋„ŒํŠธ์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ํ˜ธ์ถœ๋˜๋Š” ์ƒ๋ช…์ฃผ๊ธฐ ๋ฉ”์„œ๋“œ
  • Render ๋‹จ๊ณ„(๊ฐ€์ƒ DOM ์กฐ์ž‘ ๋‹จ๊ณ„ / ๋ถ€์ˆ˜ํšจ๊ณผ ๋น„ํ—ˆ์šฉ)์—์„œ ํ˜ธ์ถœ
  • ์—…๋ฐ์ดํŠธ๋œ ์ƒํƒœ(State) ๊ฐ’์„ ํ•„์ˆ˜์ ์œผ๋กœ ๋ฐ˜ํ™˜ํ•ด์•ผํ•จ
  • ํŒŒ๋ผ๋ฏธํ„ฐ : ์˜ค๋ฅ˜ ๊ฐ์ฒด

 

componentDidCatch ๋ฉ”์„œ๋“œ ํŠน์ง• | ์ฐธ๊ณ 

  • ์ž์‹ ์ปดํฌ๋„ŒํŠธ์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ํ˜ธ์ถœ๋˜๋Š” ์ƒ๋ช…์ฃผ๊ธฐ ๋ฉ”์„œ๋“œ
  • Commit ๋‹จ๊ณ„(๊ฐ€์ƒ DOM์„ ์‹ค์ œ DOM์— ๋ฐ˜์˜ํ•˜๊ณ  ๋ผ์ดํ”„์‚ฌ์ดํด ์‹คํ–‰ / ๋ถ€์ˆ˜ํšจ๊ณผ ํ—ˆ์šฉ)์—์„œ ํ˜ธ์ถœ
  • ์ฃผ๋กœ ์—๋Ÿฌ ๋ฆฌํฌํŒ… ์„œ๋น„์Šค์— ์—๋Ÿฌ๋ฅผ ๊ธฐ๋กํ• ๋•Œ ์‚ฌ์šฉ
  • ํŒŒ๋ผ๋ฏธํ„ฐ
    • ์ฒซ๋ฒˆ์งธ : ์˜ค๋ฅ˜ ๊ฐ์ฒด
    • ๋‘๋ฒˆ์งธ : ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ ์ปดํฌ๋„ŒํŠธ์— ๋Œ€ํ•œ ์ •๋ณด

 

import React from 'react';

// ์ฝ”๋“œ ์ฐธ๊ณ  - ๊ณต์‹ ๋ฌธ์„œ
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  // ์ž์‹ ์ปดํฌ๋„ŒํŠธ์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ํ˜ธ์ถœ๋˜๋Š” ์ƒ๋ช…์ฃผ๊ธฐ ๋ฉ”์„œ๋“œ
  // render ๋‹จ๊ณ„(๊ฐ€์ƒ DOM ์กฐ์ž‘ ๋‹จ๊ณ„ / ๋ถ€์ˆ˜ํšจ๊ณผ ๋น„ํ—ˆ์šฉ)์—์„œ ํ˜ธ์ถœ
  // ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์˜ค๋ฅ˜ ๊ฐ์ฒด๋ฅผ ์ „๋‹ฌ๋ฐ›์œผ๋ฉฐ, ์—…๋ฐ์ดํŠธ๋œ ์ƒํƒœ(State) ๊ฐ’์„ ํ•„์ˆ˜์ ์œผ๋กœ ๋ฐ˜ํ™˜ํ•ด์•ผํ•จ
  static getDerivedStateFromError(error) {
    // Fallback UI ๋ฅผ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ๋„๋ก hasError ์ƒํƒœ ์—…๋ฐ์ดํŠธ
    return { hasError: true };
  }

  render() {
    if (this.state.hasError) {
      // ์›ํ•˜๋Š” Fallback UI๋ฅผ ์ง์ ‘ ๋ Œ๋”๋งํ•  ์ˆ˜๋„ ์žˆ์Œ ex) return <h1>์—๋Ÿฌ ๋ฐœ์ƒ!</h1>;
      return this.props.fallback;
    }
    return this.props.children;
  }
}

export default ErrorBoundary;

 

ErrorBoundary๋Š” ์˜ค๋ฅ˜๋ฅผ ๊ฒ€์ถœํ•˜๊ณ  ์‹ถ์€ Suspense ํŠธ๋ฆฌ์— ์ถ”๊ฐ€ํ•œ๋‹ค. ErrorBoundary์™€ Suspense๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด UI ๋กœ์ง์„ ๋”์šฑ ์ง๊ด€์ (์„ ์–ธ์ )์œผ๋กœ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์–ด์„œ ์ข‹๋‹ค.

export default function Main() {
  // ...

  return (
    <div>
      <ErrorBoundary fallback={<Error id={id} />}> {/* ์‹คํŒจ UI */}
        <Suspense fallback={<Loading type="user" />}> {/* ๋กœ๋”ฉ UI */}
          <User resource={resource} /> {/* ์„ฑ๊ณต UI */}
          <Suspense fallback={<Loading type="posts" />}> {/* ๋กœ๋”ฉ UI */}
            <Posts resource={resource} /> {/* ์„ฑ๊ณต UI */}
          </Suspense>
        </Suspense>
      </ErrorBoundary>
      {/* ... */}
    </div>
  );
}
๋”๋ณด๊ธฐ
// ์ฝ”๋“œ ์ถœ์ฒ˜ - ๊ณต์‹ ๋ฌธ์„œ
function ProfilePage() {
  return (
    <Suspense fallback={<h1>Loading profile...</h1>}>
      <ProfileDetails />
      <ErrorBoundary fallback={<h2>Could not fetch posts.</h2>}>
        <Suspense fallback={<h1>Loading posts...</h1>}>
          <ProfileTimeline />
        </Suspense>
      </ErrorBoundary>
    </Suspense>
  );
}

 

์˜ˆ์‹œ ๋ณด๊ธฐ


์œ ์ € / ํฌ์ŠคํŠธ ๋ฐ์ดํ„ฐ๋Š” JSON Placeholder์—์„œ ๊ฐ€์ ธ์˜ค๋ฉฐ, 11๋ฒˆ์งธ ID๋ถ€ํ„ด ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์–ด์„œ ์—๋Ÿฌ UI๋ฅผ ํ‘œ์‹œํ•œ๋‹ค.

 

 

๋ ˆํผ๋Ÿฐ์Šค


 


๊ธ€ ์ˆ˜์ •์‚ฌํ•ญ์€ ๋…ธ์…˜ ํŽ˜์ด์ง€์— ๊ฐ€์žฅ ๋น ๋ฅด๊ฒŒ ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค. ๋งํฌ๋ฅผ ์ฐธ๊ณ ํ•ด ์ฃผ์„ธ์š”
๋ฐ˜์‘ํ˜•