FrontEnd Develop/Project : Wallet Guardians

๋ณธ๊ฒฉ ์—ฐ๋™ #6. ๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ์‹œ UI ๊นœ๋นก์ž„ ๋ฐฉ์ง€ (await & setTimeout ํ™œ์šฉ)์œผ๋กœ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ์ฆ๋Œ€

Frisbeen 2025. 2. 11. 17:40
 

๐Ÿ”น 1. ๋ฌธ์ œ ์ƒํ™ฉ ๋ฐ ๊ฐœ์š” 

๐Ÿ“Œ ๋ฌธ์ œ: ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋•Œ UI๊ฐ€ ๊นœ๋นก์ด๋Š” ํ˜„์ƒ ๋ฐœ์ƒ

  • React์—์„œ API ํ˜ธ์ถœ์„ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋•Œ, ๋ฐ์ดํ„ฐ๊ฐ€ ๋„์ฐฉํ•˜๊ธฐ ์ „๊นŒ์ง€ UI๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋ Œ๋”๋ง๋˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Œ
  • ์˜ˆ๋ฅผ ๋“ค์–ด, goalAmount๊ฐ€ ์ดˆ๊ธฐ์—๋Š” null์ด์ง€๋งŒ, ์„œ๋ฒ„ ์‘๋‹ต ํ›„ 100000์œผ๋กœ ๋ณ€๊ฒฝ๋  ๊ฒฝ์šฐ, ํ™”๋ฉด์ด ๊ฐ‘์ž๊ธฐ ๋ณ€ํ•˜๋ฉด์„œ ๊นœ๋นก์ด๋Š” ํ˜„์ƒ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Œ
  • ์ด๋Ÿฌํ•œ UI ๊นœ๋นก์ž„(Flash of Unstyled Content, FOUC)์€ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜(UX)์„ ์ €ํ•˜์‹œํ‚ฌ ์ˆ˜ ์žˆ์Œ

 

์ฐธ๊ณ ๋กœ

FOUC (๊นœ๋นก์ž„)์ด ๋ฐœ์ƒํ•˜๋Š” ์กฐ๊ฑด

1. ๋น„๋™๊ธฐ ์š”์ฒญ์˜ ์‘๋‹ต์ด ์ง€์—ฐ๋˜์—ˆ์„๋•Œ (await ์—†์ด ๋น„๋™๊ธฐ ์š”์ฒญ์ด ์‹คํ–‰๋ฌ์„๋•Œ)

 

2. ์ƒํƒœ ๊ฐ’์ด ๋น„๋™๊ธฐ ์‘๋‹ต ์ดํ›„ ๋ณ€๊ฒฝ๋ ๋•Œ

useState()๋กœ ๊ด€๋ฆฌํ•˜๋Š” ๊ฐ’(goalAmount ๋“ฑ)์ด ๋น„๋™๊ธฐ ์‘๋‹ต ์ดํ›„ ์—…๋ฐ์ดํŠธ๋  ๊ฒฝ์šฐ, React๊ฐ€ ๋‹ค์‹œ ๋ Œ๋”๋ง์„ ์ˆ˜ํ–‰

๋ Œ๋”๋ง์ด ๋ฐ˜๋ณต๋˜๋ฉด์„œ UI๊ฐ€ “์ดˆ๊ธฐ ์ƒํƒœ → ๋ฐ์ดํ„ฐ๊ฐ€ ์ฑ„์›Œ์ง„ ์ƒํƒœ”๋กœ ๋ณ€ํ™”ํ•˜๋ฉด์„œ ๊นœ๋นก์ž„์ด ๋ฐœ์ƒํ•จ.

 

3. ์กฐ๊ฑด๋ถ€ ๋ Œ๋”๋ง ์‹œ, ์ƒํƒœ๊ฐ€ ๋‹ค๋ฅธ UI๋ฅผ ๋ณด์—ฌ์ค„ ๊ฒฝ์šฐ

์˜ˆ๋ฅผ๋“ค๋ฉด,

{goalAmount ? <p>ํ˜„์žฌ ์˜ˆ์‚ฐ: {goalAmount}์›</p> : <p>์˜ˆ์‚ฐ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘...</p>}

์œ„์™€ ๊ฐ™์€ ์กฐ๊ฑด๋ถ€ ๋ Œ๋”๋ง์„ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ,

1. goalAmount๊ฐ€ null์ด๋ฉด "์˜ˆ์‚ฐ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘..."์ด ํ‘œ์‹œ๋จ

2. ๋น„๋™๊ธฐ ์‘๋‹ต์ด ๋„์ฐฉํ•œ ํ›„ goalAmount๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜๋ฉด ์ƒˆ๋กœ์šด UI(ํ˜„์žฌ ์˜ˆ์‚ฐ: 100000์›)๊ฐ€ ๋ Œ๋”๋ง๋จ

3. UI๊ฐ€ ๊ฐ‘์ž๊ธฐ ๋ณ€๊ฒฝ๋˜๋ฉด์„œ ๊นœ๋นก์ž„์ด ๋ฐœ์ƒํ•จ

 

4. setTimeout(), useEffect() ๋“ฑ์ด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜จ ํ›„ UI๋ฅผ ๋ณ€๊ฒฝํ•  ๋•Œ

useEffect()์—์„œ fetchBudget()์„ ์‹คํ–‰ํ•˜๊ณ  ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ UI๊ฐ€ ๋‹ค์‹œ ๋ Œ๋”๋ง๋จ

์ดˆ๊ธฐ๊ฐ’(null)๋กœ ๋ Œ๋”๋ง๋œ ํ›„, ๋ฐ์ดํ„ฐ๊ฐ€ ๋“ค์–ด์˜จ ํ›„ UI๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด์„œ ๊นœ๋นก์ž„ ๋ฐœ์ƒ ๊ฐ€๋Šฅ

๐Ÿ›  ์ƒ๊ฐํ•ด๋‚ธ ์•„์ด๋””์–ด์™€ ๋ชฉํ‘œ

โœ… ๋น„๋™๊ธฐ ์š”์ฒญ์ด ๋๋‚  ๋•Œ๊นŒ์ง€ await์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ด
โœ… ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜จ ํ›„ UI๊ฐ€ ๊ฐ‘์ž๊ธฐ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋„๋ก setTimeout()์„ ํ™œ์šฉํ•˜์—ฌ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ „ํ™˜
โœ… ๋กœ๋”ฉ ์ค‘ ์ƒํƒœ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ์‚ฌ์šฉ์ž์—๊ฒŒ ํ”ผ๋“œ๋ฐฑ์„ ์ œ๊ณตํ•˜๊ณ  ๊นœ๋นก์ž„์„ ์ตœ์†Œํ™”


๐Ÿ”น 2. ๋น„๋™๊ธฐ ์š”์ฒญ(await)์„ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๊ฐ€ ์ค€๋น„๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ๊ธฐ

๐Ÿ“Œ await ์—†์ด ์‹คํ–‰ํ•˜๋ฉด ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ

const handleLogin = async (e) => {
  e.preventDefault();
  setLoading(true);

  try {
    const data = await login(email, password);
    localStorage.setItem('accessToken', data.data.accessToken);
    localStorage.setItem('refreshToken', data.data.refreshToken);

    setFetchingBudget(true);
    const budgetAmount = fetchBudget(); // โŒ `await` ์—†์ด ์‹คํ–‰ → ์‘๋‹ต์ด ๋„์ฐฉํ•˜๊ธฐ ์ „์— ๋‹ค์Œ ์ฝ”๋“œ ์‹คํ–‰๋จ
    setFetchingBudget(false);

    console.log('๐Ÿฆ ๋กœ๊ทธ์ธ ํ›„ ๋ฐ›์€ ์˜ˆ์‚ฐ ๊ธˆ์•ก:', budgetAmount);

    // โŒ ๋ฐ์ดํ„ฐ๊ฐ€ ์•„์ง ์—†์„ ๋•Œ navigate ์‹คํ–‰ → ์ž˜๋ชป๋œ ํŽ˜์ด์ง€ ์ด๋™ ๊ฐ€๋Šฅ์„ฑ ์žˆ์Œ
    navigate(budgetAmount !== null && budgetAmount > 0 ? '/main' : '/initial');
  } catch (error) {
    console.error('๋กœ๊ทธ์ธ ์‹คํŒจ:', error);
  } finally {
    setLoading(false);
  }
};

โš ๏ธ ๋ฌธ์ œ์ 

1๏ธโƒฃ fetchBudget()์ด ์‹คํ–‰๋˜์—ˆ์ง€๋งŒ, ์‘๋‹ต์ด ๋„์ฐฉํ•˜๊ธฐ ์ „์— ๋‹ค์Œ ์ฝ”๋“œ(navigate())๊ฐ€ ์‹คํ–‰๋จ

2๏ธโƒฃ goalAmount๊ฐ€ ์•„์ง null์ด๋ฏ€๋กœ, ์ž˜๋ชป๋œ UI ์ƒํƒœ์—์„œ ํŽ˜์ด์ง€๊ฐ€ ๋ณ€๊ฒฝ๋  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์Œ

3๏ธโƒฃ ๋น„๋™๊ธฐ ์‘๋‹ต์ด ๋„์ฐฉํ•œ ํ›„ UI๊ฐ€ ๊ฐ‘์ž๊ธฐ ๋ณ€๊ฒฝ๋˜๋ฉด์„œ ๊นœ๋นก์ด๋Š” ํ˜„์ƒ ๋ฐœ์ƒ


๐Ÿ”น 3. setTimeout() + await ํ™œ์šฉํ•˜์—ฌ UI๊ฐ€ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ์ „ํ™˜๋˜๋„๋ก ํ•˜๊ธฐ

โœ… ์˜ฌ๋ฐ”๋ฅธ ์ฝ”๋“œ (await๊ณผ setTimeout์„ ์‚ฌ์šฉํ•˜์—ฌ ๊นœ๋นก์ž„ ๋ฐฉ์ง€)

const handleLogin = async (e) => {
  e.preventDefault();
  setLoading(true);

  try {
    const data = await login(email, password);
    localStorage.setItem('accessToken', data.data.accessToken);
    localStorage.setItem('refreshToken', data.data.refreshToken);

    setModalMessage({
      type: 'success',
      message: '๋กœ๊ทธ์ธ ์„ฑ๊ณต! ์˜ˆ์‚ฐ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค!',
    });

    // โœ… ์„œ๋ฒ„ ์‘๋‹ต์„ ๊ธฐ๋‹ค๋ฆด ๋•Œ `await` ์‚ฌ์šฉ
    setFetchingBudget(true);
    const budgetAmount = await fetchBudget();
    setFetchingBudget(false);

    console.log('๐Ÿฆ ๋กœ๊ทธ์ธ ํ›„ ๋ฐ›์€ ์˜ˆ์‚ฐ ๊ธˆ์•ก:', budgetAmount);

    // โœ… UI๊ฐ€ ๊ฐ‘์ž๊ธฐ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋„๋ก `setTimeout()`์„ ์‚ฌ์šฉํ•˜์—ฌ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ „ํ™˜
    setTimeout(() => {
      navigate(budgetAmount !== null && budgetAmount > 0 ? '/main' : '/initial');
    }, 500);
  } catch (error) {
    console.error('๋กœ๊ทธ์ธ ์‹คํŒจ:', error);
    setModalMessage({
      type: 'error',
      message: '๋กœ๊ทธ์ธ ์‹คํŒจ! ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.',
    });
  } finally {
    setLoading(false);
  }
};

๐Ÿ”น 4. setTimeout()์„ ์‚ฌ์šฉํ•œ ์ด์œ 

โœ… await์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ - goalAmount ๊ฐ’์ด ํ™•์‹คํžˆ ์—…๋ฐ์ดํŠธ๋จ- null์ƒํƒœ์—์„œ UI๊ฐ€ ๋ Œ๋”๋ง๋˜์ง€ ์•Š์Œ - ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ์‹œ๊ฐ„์ด ๊ธธ๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ์‘๋‹ต์„ ๋Š๋ฆฌ๊ฒŒ ์ฒด๊ฐํ•  ์ˆ˜ ์žˆ์Œ
โœ… setTimeout()์œผ๋กœ ํ™”๋ฉด ์ „ํ™˜ ๋”œ๋ ˆ์ด ์ถ”๊ฐ€ - goalAmount ๊ฐ’์ด ๋ณ€๊ฒฝ๋œ ํ›„ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ํ™”๋ฉด ์ „ํ™˜๋จ- ๊นœ๋นก์ž„ ๋ฐฉ์ง€ ํšจ๊ณผ - ๋„ˆ๋ฌด ์งง์œผ๋ฉด ํšจ๊ณผ๊ฐ€ ์—†๊ณ , ๋„ˆ๋ฌด ๊ธธ๋ฉด UX ๋ถˆํŽธํ•จ (500ms~700ms๊ฐ€ ์ ์ ˆ)
 

๐Ÿš€ ์ตœ์ข… ๊ฒฐ๋ก 

 ๋น„๋™๊ธฐ ์š”์ฒญ(await fetchBudget())์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋‹ค๋ฆด ์ˆ˜ ์žˆ์ง€๋งŒ, React์˜ ์ƒํƒœ ๋ณ€๊ฒฝ ๋ฐฉ์‹ ๋•Œ๋ฌธ์— UI๊ฐ€ ๊นœ๋นก์ผ ์ˆ˜ ์žˆ๋‹ค.
 ์ด๋Ÿฌํ•œ ๊นœ๋นก์ž„์„ ๋ฐฉ์ง€ํ•˜๋ ค๋ฉด setTimeout(500ms)์„ ์ถ”๊ฐ€ํ•˜์—ฌ ํ™”๋ฉด ์ „ํ™˜์ด ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ์ด๋ฃจ์–ด์ง€๋„๋ก ํ•ด์•ผ ํ•œ๋‹ค.

 

์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ์ฆ‰, UX๋ฅผ ๊ณ ๋ คํ•˜๋ฉด loading ์ƒํƒœ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ "๋กœ๋”ฉ ์ค‘..."์„ ํ‘œ์‹œํ•˜๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ผ ๊ฒƒ ๊ฐ™๊ธดํ•˜๋‹ค๋งŒ, ๋‚œ ๋ชจ๋‹ฌ ๋ฉ”์‹œ์ง€๋กœ ์ด๋ฅผ ์ฒ˜๋ฆฌํ—€๋‹ค.

 

 

 ์ฆ‰, ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜จ ํ›„ UI์— ๋ฐ˜์˜ํ•  ๋•Œ, await์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ , setTimeout()์„ ํ™œ์šฉํ•˜์—ฌ UI๊ฐ€ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ „ํ™˜๋˜๋„๋ก ํ•˜๋Š” ๊ฒƒ์ด ์‚ฌ์šฉ์ž ์ž…์žฅ์—์„  ์ œ์ผ