React ์„ฑ๋Šฅ ์ตœ์ ํ™”

2026. 1. 20. 03:11ยท๐Ÿ’™ ํ”„๋ก ํŠธ์—”๋“œ(FE)
728x90

1. ๋ Œ๋”๋ง ์ตœ์ ํ™”

1. ๋ถˆํ•„์š”ํ•œ ๋ Œ๋”๋ง ๋ง‰๊ธฐ

๋งŒ์•ฝ useState ์˜ ๋ณ€๊ฒฝ์œผ๋กœ ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋œ๋‹ค๋ฉด, ์ƒํƒœ์— ๊ด€๋ จ์žˆ๋Š” ์ปดํฌ๋„ŒํŠธ๋กœ ๊ตฌ์„ฑ/๋ถ„๋ฆฌํ•œ๋‹ค.

๋‹ค์Œ ์ฝ”๋“œ์˜ ๋ฌธ์ œ์ ์€?

export default function App() {
  let [color, setColor] = useState('red');
  return (
    <div>
      <input value={color} onChange={(e) => setColor(e.target.value)} />
      <p style={{ color }}>Hello, world!</p>
      <ExpensiveTree />
    </div>
  );
}

[after]

export default function App() {
  return (
    <>
      <Form />
      <ExpensiveTree />
    </>
  );
}

function Form() {
  let [color, setColor] = useState('red');
  return (
    <>
      <input value={color} onChange={(e) => setColor(e.target.value)} />
      <p style={{ color }}>Hello, world!</p>
    </>
  );
}

์ฐธ๊ณ  : https://overreacted.io/before-you-memo/

2. ์ฐธ์กฐํƒ€์ž… render๋ฐ–์— ์„ ์–ธํ•˜๊ธฐ

๋ Œ๋”๋ง ์—ญํ• ์„ ํ•˜๋Š” return ์•ˆ์— ์ฐธ์กฐ๊ฐ€ ๋˜๋Š” ํƒ€์ž…(ํ•จ์ˆ˜, ๊ฐ์ฒด, ๋ฐฐ์—ด) ์„ ์ง์ ‘ ์„ ์–ธํ•˜์ง€ ์•Š๋Š”๋‹ค.

์ด๋ ‡๊ฒŒ ๋˜๋ฉด ์—…๋ฐ์ดํŠธ ๋  ๋•Œ ๊ฐ’์„ ๋น„๊ตํ•˜๋Š” ์ฝ”๋“œ๊ฐ€ ์žˆ๋‹ค๋ฉด, ๊ทธ ๊ฒฐ๊ณผ๋Š” ๋งค๋ฒˆ ๋‹ค๋ฅด๋‹ค๊ณ  ํŒ๋‹จํ•  ๊ฒƒ์ด๋‹ค. ๋”ฐ๋ผ์„œ ๋‹ค์‹œ ๋ Œ๋”๋ง์ด ๋œ๋‹ค.

return (
 <>
  	<Title>{title is..}</Title>
	<ShowPost postClick={()=>{console.log('click handler')}} />
  </>
)

์œ„์ฝ”๋“œ์˜ postClick์€ ๋™์ผํ•œ ํ•จ์ˆ˜์ž„์—๋„ ๋งค๋ฒˆ ์ƒ์„ฑ๋˜๊ณ  ์žˆ๋‹ค.
postClick์˜ ์ธ์ž๋กœ ๋“ค์–ด๊ฐ€๋Š” ํ•จ์ˆ˜๋ฅผ jsx ๋ฐ–์— ์„ ์–ธํ•˜๊ณ , ํ•จ์ˆ˜ ์บ์‹œ์˜ ํ™œ์šฉ ๊ธฐํšŒ๋ฅผ ๋งŒ๋“ค์ž.

[๊ฐœ์„ ์•ˆ]

  • ์ปดํฌ๋„ŒํŠธ์˜ props๋‚˜ state๋ฅผ ์ฐธ์กฐํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค๋ฉด, ์•„์˜ˆ ์ปดํฌ๋„ŒํŠธ ํ•จ์ˆ˜ ๋ฐ”๊นฅ์œผ๋กœ ๋นผ๊ธฐ
  • ๋‚ด๋ถ€์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ํ•„์š”ํ•˜๋‹ค๋ฉด useCallback ํ›…์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•จ์ˆ˜๋ฅผ ๋ฉ”๋ชจ์ด์ œ์ด์…˜

3. useCallback : ํ•จ์ˆ˜ ์บ์‹œ

ํ•จ์ˆ˜๋ฅผ props๋กœ ์ „๋‹ฌํ• ๋•Œ ๋งค๋ฒˆ ์ƒˆ๋กœ์šด ํ•จ์ˆ˜๊ฐ€ ์ „๋‹ฌ๋  ์ˆ˜ ์žˆ๋‹ค.
๋งค๋ฒˆ ์ƒˆ๋กœ์šด ํ•จ์ˆ˜๋ฅผ ์ƒ์„ฑํ•  ํ•„์š”๋Š” ์—†๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฅผ ์žฌ์‚ฌ์šฉํ•˜๋„๋ก ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Œ.

props๋กœ ํ•จ์ˆ˜๋ฅผ ์ „๋‹ฌ๋ฐ›๋Š” ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ๋Š” ์ƒˆ๋กœ์šด props๊ฐ€ ์ „๋‹ฌ๋๊ธฐ ๋•Œ๋ฌธ์— ๋‹ค์‹œ ๋ Œ๋”๋ง ํ•  ์ˆ˜ ์žˆ๋‹ค.

์ƒˆ๋กœ์šด ํ•จ์ˆ˜๋ฅผ ๊ณ„์† ์ƒ์„ฑํ•˜์ง€ ์•Š๊ณ  ๊ธฐ์กด ํ•จ์ˆ˜๋ฅผ ๊ธฐ์–ตํ•ด์„œ ์‚ฌ์šฉํ•˜๊ณ , ์ด๋ฅผ props๋กœ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ์ด React์—์„œ๋Š” ๊ถŒ์žฅ๋˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

 

์—ฌ๊ธฐ์„œ๋„ ๋‘๋ฒˆ์งธ ์ธ์ž([a,b])๊ฐ€ ํ•จ์ˆ˜ ์žฌ์‚ฌ์šฉ์„ ํ• ์ง€์— ๋Œ€ํ•œ ์กฐ๊ฑด์—ญํ• ์„ ํ•œ๋‹ค.
์˜์กด์„ฑ๋ฐฐ์—ด์„ ์„ค์ •ํ•˜์ง€ ์•Š์œผ๋ฉด ์บ์‹œ๋˜์ง€ ์•Š์Œ.

 

์ฐธ๊ณ )

  • ๊ณผ์—ฐ ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ์— ์ „๋‹ฌ๋˜๋Š” ํ•จ์ˆ˜๋ฅผ ์บ์‹œํ•ด์•ผ ํ• ๊นŒ? ๐Ÿค”
  • ์บ์‹œ๋œ ํ•จ์ˆ˜๋ฅผ ํ•˜์œ„์ปดํฌ๋„ŒํŠธ์— ์ „๋‹ฌํ•˜๋ฉด ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ๋Š” ๋ฆฌ๋ Œ๋”๋ง์ด ์•ˆ๋˜๋‚˜? (๋ฆฌ์•กํŠธ ํŠน์ง•)

4. useEffect ๋งค๋ฒˆ ์‹คํ–‰๋˜์ง€ ์•Š๊ฒŒ ํ•˜๊ธฐ

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes

๋‘ ๋ฒˆ์งธ ์ธ์ž๋Š” ์•„๋ž˜ ๋‹ค๋ฅธ Hook์—์„œ๋„ ์ฝœ๋ฐฑํ•จ์ˆ˜์˜ ์‹คํ–‰์— ๋Œ€ํ•œ ์กฐ๊ฑด์œผ๋กœ ํ™œ์šฉ๋œ๋‹ค.

=> useEffect, useMemo, useCallback

5. React.memo : ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง ๊ฒฐ๊ณผ ์žฌ์‚ฌ์šฉ

๋ถˆํ•„์š”ํ•˜๊ฒŒ ์ž๊ธฐ ์ž์‹ ์„ ๋ Œ๋”๋ง ํ•˜์ง€ ์•Š๊ฒŒ ํ•˜๊ธฐ.

์•„๋ž˜ ์ฝ”๋“œ์—์„œ ShowCountInfo์— ์ „๋‹ฌ๋˜๋Š” props๊ฐ’์€ ๋Œ€๋ถ€๋ถ„ ๋™์ผํ•˜๋‹ค. ShowCountInfo ์ปดํฌ๋„ŒํŠธ๋Š” ๋งค๋ฒˆ ๋™์ผํ•œ ๋ Œ๋”๋ง ๊ฒฐ๊ณผ๋ฅผ ๋งŒ๋“ค๊ณ  ์žˆ๋Š” ๊ฒƒ์œผ๋กœ ๋ณด์ธ๋‹ค.

[App.jsx]

// [App.jsx]
const App = () => {
  // 1. ์ž์‹(ShowCountInfo)์—๊ฒŒ ์ „๋‹ฌ๋  ๋ฐ์ดํ„ฐ (์ƒํƒœ๋กœ ๊ด€๋ฆฌ)
  const [posts] = useState([{ id: 1 }, { id: 2 }]); 
  const [pickedCount] = useState(1);

  // 2. ์ž์‹๊ณผ๋Š” ์ƒ๊ด€์—†๋Š” ๋ถ€๋ชจ๋งŒ์˜ ์ƒํƒœ (์˜ˆ: ๋‹คํฌ๋ชจ๋“œ, ํƒ€์ด๋จธ, ์ž…๋ ฅ์ฐฝ ๋“ฑ)
  const [typedText, setTypedText] = useState("");

  return (
    <div>
      {/* ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•  ๋•Œ๋งˆ๋‹ค App์€ ๋ฆฌ๋ Œ๋”๋ง๋˜์ง€๋งŒ, 
          ShowCountInfo๋กœ ๋‚ด๋ ค๊ฐ€๋Š” posts, pickedCount๋Š” ๋ณ€ํ•˜์ง€ ์•Š์Œ */}
      <input 
        value={typedText} 
        onChange={(e) => setTypedText(e.target.value)} 
        placeholder="์ž์‹๊ณผ ์ƒ๊ด€์—†๋Š” ์ž…๋ ฅ์ฐฝ"
      />
      
      <p>์ž…๋ ฅ ์ค‘: {typedText}</p>

      {/* React.memo๊ฐ€ ์—†๋‹ค๋ฉด, ๊ธ€์ž ํ•˜๋‚˜ ์น  ๋•Œ๋งˆ๋‹ค ์ด ์ปดํฌ๋„ŒํŠธ๋„ ๊ณ„์† ๋‹ค์‹œ ๊ทธ๋ ค์ง */}
      <ShowCountInfo postCount={posts.length} pickedCount={pickedCount} />
    </div>
  );
};

๊ฐœ์„  ํ•„์š”.
React.memo ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด์„œ ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง ๊ฒฐ๊ณผ๋ฅผ ์บ์‹œํ•˜๋„๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค.

[ShowCountInfoMemo.jsx]

const ShowCountInfo = ({postCount, pickedCount}) => {
    return (
        <div>
            <span>์ด {postCount}์˜ post ์ค‘ {pickedCount}๊ฐœ๊ฐ€ ์„ ํƒ๋์Šต๋‹ˆ๋‹ค.</span>
        </div>
    )
}

const ShowCountInfoMemo = React.memo(ShowCountInfo);

์ฐธ๊ณ ) ์กฐ๊ฑด์ƒํ™ฉ์„ ํ†ตํ•ด์„œ re-rendering์„ ๊ฒฐ์ •ํ•  ์ˆ˜ ์žˆ์Œ.

  • false๋ฉด ์ด์ „ props์™€ ๋‹ค์Œ props๊ฐ€ ๋‹ฌ๋ผ re-rendenring
const PressItemMemo = React.memo(PressItem, (prev, next) => {
   return (prev.newsData === next.newsData)
})

6. React.memo, UseCallback์„ ํ™œ์šฉํ•œ ์‹œ๋‚˜๋ฆฌ์˜ค.

๋จผ์ € props๋กœ function์„ ์ „๋‹ฌํ•˜๊ณ  ์‹ถ์€๊ฒฝ์šฐ์—๋Š”, useCallack์„ ํ†ตํ•ด ํ•œ๋ฒˆ ๊ฐ์‹ผ function์„ ์ „๋‹ฌํ•œ๋‹ค.
์ „๋‹ฌ๋ฐ›์€ ์ปดํฌ๋„ŒํŠธ๋Š” React.memo๋ฅผ ํ†ตํ•ด์„œ ๊ฐ์‹ผ๋‹ค.
React.memo๋Š” props๊ฐ€ ๋™์ผํ•˜๋‹ค๋ฉด ๋‹ค์‹œ ๋ Œ๋”๋ง๋˜์ง€ ์•Š๋„๋ก ํ•œ๋‹ค.
๋”ฐ๋ผ์„œ useCallback์„ ํ†ตํ•ด์„œ ๋™์ผํ•œ props๋ฅผ ์ „๋‹ฌ๋ฐ›์•˜๋‹ค๋ฉด ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋‹ค์‹œ ๋ Œ๋”๋ง ๋˜์ง€ ์•Š๊ณ , ๋ Œ๋”๋ง ๊ฒฐ๊ณผ๋ฅผ ์žฌ์‚ฌ์šฉํ•˜๊ฒŒ ๋œ๋‹ค

7. ์‹ค์Šต

https://codesandbox.io/s/react-react-memo-usecallback-problem-mrrqn

  1. Child ์ปดํฌ๋„ŒํŠธ์˜ ๋ถˆํ•„์š”ํ•œ Re-rendering ํ™•์ธ
  2. Child ์ปดํฌ๋„ŒํŠธ์— React.memo ์ ์šฉ
  3. useCallback ์„ ์‚ฌ์šฉํ•ด๋ณด๊ธฐ (์˜์กด์„ฑ ๋ฐฐ์—ด ํ•„์š”, [])

8. useMemo : ์—ฐ์‚ฐ ๊ฒฐ๊ณผ ์บ์‹œ

์•„๋ž˜ ์ฝ”๋“œ๋Š” ๋ฌด์—‡์ด ๋ฌธ์ œ์ธ๊ฐ€?

import React, { useState, useMemo } from 'react';

const Child = ({ items }) => {
  const [count, setCount] = useState(0);

  const selectedItem = items.find((item) => {
    console.log('finding value');
    return item === 9;
  }); 

return (
    <>
        <h1>Count: {count} </h1>
        <button onClick={() => setCount(count + 1)}>Increment</button>
        <h1>selectedItem : {selectedItem}</h1>
    </>
    );
};

const App = () => {
    const items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    return <Child items={items}></Child>;    
};

export default App;

๋ฆฌ๋ Œ๋”๋ง์‹œ ๋ถˆํ•„์š”ํ•œ ์ค‘๋ณต๊ณ„์‚ฐ์ด ์ผ์–ด๋‚˜์ง€ ์•Š๋„๋ก dependency ์„ค์ •์„ ํ•ด์„œ ์บ์‹œ๋ฅผ ํ•˜๋„๋ก ํ•  ์ˆ˜ ์žˆ์Œ.
์ฆ‰, ๋ฐ˜๋ณต์ ์ธ ๋ฆฌ์•กํŠธ ๋ Œ๋”๋ง ๊ณผ์ •์—์„œ, ๋™์ผํ•œ ๊ณ„์‚ฐ์ด ์ผ์–ด๋‚˜๋Š” ๋ฌธ์ œ๋ฅผ ๋ง‰์„ ์ˆ˜๊ฐ€ ์žˆ์Œ.

const selectedItem = useMemo(() => {
    return items.find((item) => {
        console.log('finding value');
        return item === 9;
    });
}, [items]);

9. profiling : ๋ Œ๋”๋ง ์ตœ์ ํ™” ํ™•์ธ

๊ฐœ๋ฐœ์ž๋„๊ตฌ-React์˜ Profiler ํŒจ๋„์„ ํ™•์ธํ•œ๋‹ค.

'profiling๋ฒ„ํŠผ'์„ ๋ˆŒ๋Ÿฌ์„œ ๋ Œ๋”๋ง๊ณผ์ •์„ ๋…นํ™”ํ•œ๋‹ค.

  • ๋ช‡๋ฒˆ ์‹ค์ œ ๋ Œ๋”๋ง(commit)์ด ๋ฐœ์ƒํ–ˆ๋Š”์ง€ ์‚ดํŽด๋ณธ๋‹ค. ์˜ˆ์ƒ๊ณผ ๋‹ฌ๋ฆฌ ๋งŽ์€ ๋ Œ๋”๋ง์ด ์ผ์–ด๋‚˜๊ณ  ์žˆ์ง€ ์•Š๋Š”์ง€ ํ™•์ธํ•œ๋‹ค.
  • ๋งค๋ฒˆ ๋ Œ๋”๋ง์ด ๋ฐœ์ƒํ• ๋•Œ๋งˆ๋‹ค ๋™์ผํ•œ ๋ Œ๋”๋ง์ด ์ผ์–ด๋‚˜๋Š” ๊ฒƒ์€ ์—†๋Š”์ง€ ํ™•์ธํ•œ๋‹ค.
    • React.memo๋ฅผ ์‚ฌ์šฉํ• ๋•Œ ๊ฐ™์€ ๋ Œ๋”๋ง์ด ์ผ์–ด๋‚˜์ง€ ์•Š๋Š” ๊ฒƒ๋„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ํŠน๋ณ„ํžˆ ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š” ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง์€ ์—†๋Š”๊ฐ€? ๋ณ‘๋ชฉ์ด ๋˜๋Š” ์ง€์ ์„ ์ฐพ๊ณ  ์›์ธ์„ ์ฐพ์•„๋ณธ๋‹ค.

Profiler ์ค‘์— ๋ถˆํ•„์š”ํ•œ component tree๊ฐ€ ๋งŽ์ด ๋ณด์ธ๋‹ค๋ฉด ์„ค์ •๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ์„œ, ํ•„ํ„ฐ๋ง์„ ํ•  ์ˆ˜ ์žˆ๋‹ค. (Context, forwardRef ๋“ฑ์„ ์ œ๊ฑฐํ•ด๋ณด์ž)

10. ์„œ๋ฒ„์ปดํฌ๋„ŒํŠธ

  • ์„œ๋ฒ„์—์„œ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅํ•œ ๋ถ€๋ถ„์€ ์„œ๋ฒ„์—์„œ ์ฒ˜๋ฆฌํ•˜๋˜, React ๋ฌธ๋ฒ•์„ ์‚ฌ์šฉ.
  • ์„œ๋ฒ„์—์„œ ์ฒ˜๋ฆฌํ•œ ์ฝ”๋“œ๋Š” ํด๋ผ์ด์–ธํŠธ์— ๋‚ด๋ ค์ค„ ํ•„์š”๊ฐ€ ์—†์Œ -> ๋ฒˆ๋“ค๋ง ์‚ฌ์ด์ฆˆ ๊ฐ์†Œ
  • ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ๋งŽ์€ ๊ฒฝ์šฐ ์„œ๋ฒ„์ปดํฌ๋„ŒํŠธ ํ™œ์šฉ์ด ์œ ๋ฆฌํ•จ

์˜ˆ์‹œ์ฝ”๋“œ

// Server Component
import Expandable from './Expandable';

async function Notes() {
  const notes = await db.notes.getAll();
  return (
    <div>
      {notes.map(note => (
        <Expandable key={note.id}>
          <p note={note} />
        </Expandable>
      ))}
    </div>
  )
}

์„œ๋ฒ„์ปดํฌ๋„ŒํŠธ์˜ ๊ฒฐ๊ณผ(RSC Payload) ๋Š” ์•„๋ž˜์˜ ํ˜•์‹์œผ๋กœ ์ „์†ก๋จ


2. React ๊ธฐํƒ€ ์ตœ์ ํ™”

1. Code-splitting

React.lazy ๋ฅผ ํ™œ์šฉํ•œ dynamic import (ES Modules ์ŠคํŽ™)

  • TC39 stage 4( Finished )

React.lazy๋กœ ์„ ์–ธ๋œ ์ปดํฌ๋„ŒํŠธ๋Š” production build์—์„œ ๋ณ„๋„ ํŒŒ์ผ๋กœ ๋ถ„๋ฆฌ๋จ(code - splitting).

# ์ฐธ๊ณ . React.suspense ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•ด์•ผ ํ•จ

  • ๋น„๋™๊ธฐ ์š”์ฒญ์ด ์˜ค๊ธฐ ์ „ pending์ƒํƒœ์—์„œ ํšจ๊ณผ์ ์œผ๋กœ fallback UI(loading~~)๋ฅผ ํšจ๊ณผ์ ์œผ๋กœ ๋…ธ์ถœ ํ•  ์ˆ˜ ์žˆ์Œ.
  • ๋ Œ๋”๋ง ์ดํ›„ fetch๊ฐ€ ์•„๋‹Œ, fetch์™€ ๋™์‹œ์— ๋ Œ๋”๋ง์ด ๊ฐ€๋Šฅํ•œ ๊ธฐ์ˆ 
  • ์ฐธ๊ณ : https://react.dev/reference/react/Suspense

์˜ค๋ฆฌ์ง€๋‚  ์ฝ”๋“œ

import './App.css';
import { NewsListCount } from './NewsList';
import { PressNameList } from './PressNameList';
import React, { Suspense } from 'react';
import { TestComponent } from './TestComponent.jsx';
import PressDetail from './PressDetail';

function App() {
    return (
        <div className="App">
            <TestComponent />
            <Suspense fallback="loading...">
                <h1> hello world </h1>
                <NewsListCount />
                <PressNameList />
                <PressDetail />
            </Suspense>
        </div>
    );
}

export default App;

code splitting ์ž‘์—…๊ฒฐ๊ณผ
import './App.css';
import { NewsListCount } from './NewsList';
import { PressNameList } from './PressNameList';
import React, { Suspense } from 'react';
import { TestComponent } from './TestComponent.jsx';

//lazy๋กœ ๊ฐ์‹ธ์ฃผ๊ธฐ
const PressDetailLazy = React.lazy(() => import('./PressDetail'));

function App() {
    return (
        <div className="App">
        <TestComponent />
        <Suspense fallback="loading...">
            <h1> hello world </h1>
            <NewsListCount />
            <PressNameList />
            <Suspense fallback="loading...">
            	<PressDetailLazy />
            </Suspense>
        </Suspense>
        </div>
    );
}

export default App;

code-splitting & on-demand lazy loading
import './App.css';
import { NewsListCount } from './NewsList';
import { PressNameList } from './PressNameList';
import React, { Suspense } from 'react';
import { useRecoilValue } from 'recoil';
import { currentDetailPressIDAtom } from './Store';
import {TestComponent} from './TestComponent.jsx';
const PressDetailLazy = React.lazy(() => import('./PressDetail'));

function App() {
    const currentPressId = useRecoilValue(currentDetailPressIDAtom);

    return (
        <div className="App">
            <TestComponent />
            <Suspense fallback="loading...">
                <h1>recoil fetch test</h1>
                <NewsListCount />
                <PressNameList />
                {currentPressId && (
            	    <Suspense fallback="loading...">
            	        <PressDetailLazy />
            	    </Suspense>
                )}
            </Suspense>
        </div>
    );
}

export default App;

react-router ์—์„œ์˜ lazy-loading
function App() {
    const [bNewFormView, toggleNewFormView] = useState(false);

    const MileStoneViewLazy = React.lazy(() => import('./views/MileStoneView/MileStoneView'));
    const GNBLazy = React.lazy(() => import('./views/GNB.js'));

    return (
        <Router>
            <Main>
                <GlobalStyle />
                <Suspense fallback={<div>Loading...GNB</div>}>
                    <GNBLazy {...{ toggleNewFormView }} />
                </Suspense>
              
                <Route path="/labels">
                    <Content
                        bNewFormView={bNewFormView}
                        toggleNewFormView={toggleNewFormView}
                    ></Content>
                </Route>

                <Route path="/milestone">
                    <Suspense fallback={<div>Loading...GNB</div>}>
                        <MileStoneViewLazy></MileStoneViewLazy>
                    </Suspense>
                </Route>
            </Main>
        </Router>
    );
}

2. ๋ฐ์ดํ„ฐ ํŽ˜์นญ๊ณผ Next.js์—์„œ์˜ Suspense

Next.js(App Router)์—์„œ์˜ Suspense๋ฅผ ์ž˜ ์‚ฌ์šฉํ•จ.
์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋™์•ˆ HTML์„ ์กฐ๊ฐ๋‚ด์–ด ๋จผ์ € ๋ณด๋‚ด์ฃผ๋Š” '์ŠคํŠธ๋ฆฌ๋ฐ(Streaming)'์„ ์œ„ํ•ด ์‚ฌ์šฉ.

2.1 loading.js : ํŽ˜์ด์ง€ ๋‹จ์œ„์˜ ์ž๋™ ๋กœ๋”ฉ
ํŠน์ • ๊ฒฝ๋กœ ํด๋”์— loading.js๋ฅผ ๋งŒ๋“ค๋ฉด, Next.js๋Š” ํŒŒ์ผ ์‹œ์Šคํ…œ์„ ์ธ์‹ํ•˜์—ฌ ํ•ด๋‹น ๊ฒฝ๋กœ์˜ layout.js์™€ page.js ์‚ฌ์ด์— ์ž๋™์œผ๋กœ Suspense ๊ฒฝ๊ณ„๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.

app/
 โ””โ”€ dashboard/
     โ”œโ”€ loading.js 
     โ””โ”€ page.js    

Next.js๊ฐ€ ๋‚ด๋ถ€์ ์œผ๋กœ ๋ Œ๋”๋งํ•˜๋Š” ์ฝ”๋“œ:

<Layout>
  <Suspense fallback={<Loading />}>
    <Page />
  </Suspense>
</Layout>

2.2 ์ˆ˜๋™ Suspense : ์ปดํฌ๋„ŒํŠธ ๋‹จ์œ„์˜ ์„ธ๋ฐ€ํ•œ ์ œ์–ด
Suspense๋Š” ํŽ˜์ด์ง€ ๋‚ด์˜ ํŠน์ • ๋ถ€๋ถ„๋“ค์„ ๊ฐ๊ธฐ ๋‹ค๋ฅธ ํƒ€์ด๋ฐ์— ๋กœ๋”ฉํ•˜๊ณ  ์‹ถ์„ ๋•Œ ์‚ฌ์šฉ

//(Server Component)
import { Suspense } from 'react';
import { PostList, PostListSkeleton } from './PostList';

export default function Page() {
  return (
    <section>
      <h1>๋‚˜์˜ ๋ธ”๋กœ๊ทธ</h1>
      {/* ํ…์ŠคํŠธ๋Š” ์ฆ‰์‹œ ๋…ธ์ถœ๋˜๊ณ , ์•„๋ž˜ PostList๋งŒ ๋ฐ์ดํ„ฐ ์ค€๋น„ ์‹œ์ ์— ๋…ธ์ถœ */}
      <Suspense fallback={<PostListSkeleton />}>
        <PostList /> 
      </Suspense>
    </section>
  );
}

3. React new features

1. useTransition ํ›…

  • ์ƒํƒœ ์—…๋ฐ์ดํŠธ๋ฅผ ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„๋กœ ์ฒ˜๋ฆฌํ•˜์—ฌ UI๊ฐ€ ๋Š๊ธฐ์ง€ ์•Š๋„๋ก ์ตœ์ ํ™”
  • ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค๋Š” ์ฆ‰์‹œ ๋ฐ˜์‘ํ•˜๊ณ , ์ƒํƒœ ์—…๋ฐ์ดํŠธ๋Š” ๋ฐฐ๊ฒฝ์—์„œ ์ง„ํ–‰๋จ
  • ๋ถ€๋“œ๋Ÿฌ์šด UX๋ฅผ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Œ (Interruptible)
function SimpleTransitionExample() {
  const [count, setCount] = useState(0);
  const [isPending, startTransition] = useTransition();

  const increment = () => {
    startTransition(() => {
      setCount(prev => prev + 1);
    });
  };
  .....
  • useTransition์˜ ํ•ต์‹ฌ ๊ฐœ๋…
    • ์ „ํ™˜์€ ์ค‘๋‹จ ๊ฐ€๋Šฅํ•จ (Interruptible)
      → ์ด์ „ ์ƒํƒœ ๋ณ€๊ฒฝ์ด ๋๋‚˜์ง€ ์•Š์•„๋„, ์ƒˆ๋กœ์šด ์ž…๋ ฅ์ด ๋“ค์–ด์˜ค๋ฉด ์ฆ‰์‹œ ๋ฐ˜์‘ ๊ฐ€๋Šฅ
    • ๋ถˆํ•„์š”ํ•œ ๋กœ๋”ฉ ์ธ๋””์ผ€์ดํ„ฐ ๋ฐฉ์ง€
      → ํ™”๋ฉด ์ „์ฒด๋ฅผ ๋ฎ๋Š” ์Šคํ”ผ๋„ˆ ์—†์ด, ์ด์ „ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•˜๋ฉด์„œ ์—…๋ฐ์ดํŠธ๊ฐ€ ํ•„์š”ํ•œ ์‹œ์ ์— UI๋ฅผ ์ „ํ™˜
    • ๋Œ€๊ธฐ ์ค‘์ธ ์ž‘์—…์ด ์™„๋ฃŒ๋œ ํ›„ ์ตœ์ข… UI ํ‘œ์‹œ
      → ๋น„๋™๊ธฐ ์ž‘์—…์ด ๋๋‚  ๋•Œ๊นŒ์ง€ ๋ถˆ์™„์ „ํ•œ ์ƒํƒœ๋ฅผ ๋ณด์—ฌ์ฃผ์ง€ ์•Š์Œ
  • ์–ธ์ œ ์‚ฌ์šฉํ•˜์ง€?
    • ๋‹ค๋ฅธ UX์—๊ฒŒ ์–‘๋ณดํ•˜๊ณ  ์‹ถ์„๋•Œ.
    • ๋ถˆํ•„์š”ํ•œ loading indicator ๋ฅผ ๋…ธ์ถœํ•˜์ง€ ์•Š๊ณ , ์ด์ „ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•˜๋ฉด์„œ ๋‹ค์Œ ์ƒํƒœ ์ „ํ™˜์„ ํ•˜๋ ค๊ณ  ํ• ๋•Œ.
      • ์ „ํ™˜๊ณผ์ •์€ useTransition์—์„œ ์ œ๊ณตํ•˜๋Š” ์ƒํƒœ๊ฐ’(pending)์œผ๋กœ ํ™•์ธ ๊ฐ€๋Šฅ.
  • ์˜ˆ) https://react.dev/reference/react/useTransition#preventing-unwanted-loading-indicators

2. useDeferredValue ํ›…

  • ํŠน์ • ์ƒํƒœ์˜ ์—…๋ฐ์ดํŠธ๋ฅผ ์ง€์—ฐ์‹œ์ผœ์„œ ์ž์‹ ์ปดํฌ๋„ŒํŠธ์— ์ „๋‹ฌ.
  • ๋ Œ๋”๋ง ์ž์ฒด๋ฅผ ์ง€์—ฐ์‹œํ‚ค๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ ์ƒํƒœ ๊ฐ’ ์ „๋‹ฌ์„ ๋Šฆ์ถฐ์„œ ๋ฆฌ๋ Œ๋” ํƒ€์ด๋ฐ์„ ๋ฏธ๋ฃธ.
  • ์ด๋กœ ์ธํ•ด ๊ณ ๋นˆ๋„ ์ƒํƒœ ์—…๋ฐ์ดํŠธ๊ฐ€ UI ๋ Œ๋”๋ง์— ๋ฏธ์น˜๋Š” ์˜ํ–ฅ์„ ์ค„์ž„.
    • ์‹ค์‹œ๊ฐ„์€ ์•„๋‹ˆ๊ณ  ํ•œ ํ…œํฌ ๋А๋ฆฌ๊ฒŒ ๋™์ž‘ํ•จ.
    • ๊ทธ๋™์•ˆ ์ž์‹์€ ์ด์ „ ๊ฒฐ๊ณผ๋ฅผ ๊ณ„์† ๋ณด์—ฌ์คŒ.
    • ์ตœ์ข… ์ƒํƒœ๋งŒ ๋ฐ˜์˜๋˜๋ฏ€๋กœ ์ค‘๊ฐ„ ์—…๋ฐ์ดํŠธ๋ฅผ ๊ฑด๋„ˆ๋›ธ ์ˆ˜ ์žˆ์–ด ์„ฑ๋Šฅ์— ์œ ๋ฆฌํ•จ.
    • ๋ Œ๋”๋ง ์ž์ฒด๋ฅผ ์ง€์—ฐ์‹œํ‚ค๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ ์ƒํƒœ ๊ฐ’ ์ „๋‹ฌ์„ ๋Šฆ์ถฐ์„œ ๋ฆฌ๋ Œ๋” ํƒ€์ด๋ฐ์„ ๋ฏธ๋ฃธ.
  const [input, setInput] = useState('');
  const deferredInput = useDeferredValue(input);

  return (
    <div>
      <input type="text" value={input} onChange={(e) => setInput(e.target.value)} />
      <div>์ง€์—ฐ๋œ ์ž…๋ ฅ๊ฐ’: {deferredInput}</div>
      ....
  • ์–ธ์ œ ์‚ฌ์šฉํ•˜์ง€?
    • ๋นˆ๋ฒˆํ•œ ์—…๋ฐ์ดํŠธ๊ฐ€ ๋งŽ์•„์„œ UX์— ๋ฐฉํ•ด๊ฐ€ ๋ ๋•Œ. ๋ Œ๋”๋ง์„ ์•„์˜ˆ ์ง€์—ฐ์‹œ์ผœ ๋ฒ„๋ฆฐ๋‹ค.
    • ์ด๋Ÿฐ์ ์—์„œ transition ๋ณด๋‹ค ๋” ์„ฑ๋Šฅ์ƒ ์œ ๋ฆฌํ•˜๊ฒŒ ๋™์ž‘ํ•  ์ˆ˜ ์žˆ์Œ.
    • ์˜ˆ)
      https://react.dev/reference/react/useDeferredValue

3. React Compiler (React 19 +)

  • ๋ฆฌ์•กํŠธ ์ปดํŒŒ์ผ๋Ÿฌ๋Š” ์ฝ”๋“œ์˜ ์ตœ์ ํ™”๋ฅผ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•˜์—ฌ ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ
  • ์˜ˆ) ๋ถˆํ•„์š”ํ•œ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์ œ๊ฑฐํ•˜๊ฑฐ๋‚˜ ์บ์‹œ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜์—ฌ ๋ Œ๋”๋ง ์„ฑ๋Šฅ์„ ๊ฐœ์„ 
    -๊ด€๋ จ API : useMemo, useCallback, React.memo

4. ์ž๋™๋ฐฐ์นญ ๊ฐœ์„ 

  • ์—ฌ๋Ÿฌ ์ƒํƒœ ์—…๋ฐ์ดํŠธ๋ฅผ ํ•˜๋‚˜์˜ ๋ฆฌ๋ Œ๋”๋ง์œผ๋กœ ๋ฌถ์–ด ์ฒ˜๋ฆฌํ•˜์—ฌ ์„ฑ๋Šฅ ์ตœ์ ํ™”.
  • React 18 ์ด์ „: React ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ๋‚ด๋ถ€์—์„œ๋Š” ์ž๋™ ๋ฐฐ์นญ์ด ์ ์šฉ๋˜์—ˆ์œผ๋‚˜, setTimeout, Promise, ๋„ค์ดํ‹ฐ๋ธŒ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ๋‚ด๋ถ€์—์„œ๋Š” ์ ์šฉ๋˜์ง€ ์•Š์•˜์Œ.
  • React 18๋ถ€ํ„ฐ: setTimeout, Promise, ๋„ค์ดํ‹ฐ๋ธŒ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์—์„œ๋„ ์ž๋™ ๋ฐฐ์นญ์ด ์ ์šฉ๋จ.

5. ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ

  • ์„œ๋ฒ„์—์„œ ๋ Œ๋”๋ง๋˜์–ด ํด๋ผ์ด์–ธํŠธ๋กœ ์ „๋‹ฌ๋˜๋Š” ์ปดํฌ๋„ŒํŠธ๋กœ, ์ดˆ๊ธฐ ๋กœ๋”ฉ ์†๋„ ํ–ฅ์ƒ๊ณผ ๋ฒˆ๋“ค ํฌ๊ธฐ ๊ฐ์†Œ์— ๋„์›€์„ ์คŒ
// ์„œ๋ฒ„์—์„œ ๋™์ž‘ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ 
export default async function ServerComponent() {
  const data = await fetchDataFromDatabase(); //DB์—ฐ๋™ํ•จ์ˆ˜
  return (
    <div>
      <h1>์„œ๋ฒ„์—์„œ ๊ฐ€์ ธ์˜จ ๋ฐ์ดํ„ฐ:</h1>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
}

6. ์„œ๋ฒ„ ์•ก์…˜(Server Action)

  • ์„œ๋ฒ„์—์„œ ์‹คํ–‰๋˜๋Š” ๋น„๋™๊ธฐ ํ•จ์ˆ˜๋กœ, ํด๋ผ์ด์–ธํŠธ์—์„œ ํ˜ธ์ถœํ•˜์—ฌ ์„œ๋ฒ„ ์ธก ๋กœ์ง์„ ์‹คํ–‰.
  • ํผ ์ฒ˜๋ฆฌ, ์ƒํƒœ ์—…๋ฐ์ดํŠธ, ๋ฐ์ดํ„ฐ ์ €์žฅ ๋“ฑ์— ์‚ฌ์šฉ๋จ.
  • 'use server' ์ง€์‹œ์–ด๋ฅผ ํฌํ•จํ•˜์—ฌ ์ •์˜.
  • ์„œ๋ฒ„์•ก์…˜(์„œ๋ฒ„์—์„œ ๋™์ž‘ํ•˜๋Š” ํ•จ์ˆ˜)
// Server Action
export async function handleFormSubmission(formData) {
  'use server';
  const name = formData.get('name');
  await saveToDatabase(name); // ์„œ๋ฒ„์—์„œ ์ง์ ‘ ๋ฐ์ดํ„ฐ ์ €์žฅ
}
  • ์„œ๋ฒ„ ์•ก์…˜์„ ํ˜ธ์ถœํ•˜๋Š” ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ
'use client';
//์„œ๋ฒ„ํ•จ์ˆ˜๋ฅผ ๊ฐ€์ ธ์™€์„œ ์—ฐ๋™ํ•œ๋‹ค
import { handleFormSubmission } from './serverActions';

export default function ClientComponent() {
  return (
    <form action={handleFormSubmission}>
      <input type="text" name="name" placeholder="์ด๋ฆ„ ์ž…๋ ฅ" />
      <button type="submit">์ œ์ถœ</button>
    </form>
  );
}

์„œ๋ฒ„์•ก์…˜์€ useActionState ํ›…๊ณผํ•จ๊ป˜ ์‚ฌ์šฉํ•˜์—ฌ, ๋กœ๋”ฉ/์—๋Ÿฌ ์ƒํƒœ๋ฅผ ์‰ฝ๊ฒŒ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Œ.

 

'๐Ÿ’™ ํ”„๋ก ํŠธ์—”๋“œ(FE)' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

FE ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ(FE ์•„ํ‚คํ…์ณ)  (0) 2026.01.20
React ์ƒํƒœ๊ด€๋ฆฌ ( ์ง€์—ญ, ์ „์—ญ, ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ )  (0) 2026.01.20
CSR / SSR / SSG / ISR ๋ Œ๋”๋ง ๋ฐฉ์‹ ๋น„๊ต  (1) 2026.01.17
Next.js 16 + Turbopack + pnpm ๋ชจ๋…ธ๋ ˆํฌ: Docker ๋นŒ๋“œ ์™„์ „ ์ •๋ณต  (0) 2026.01.17
์ง€๋„ ๋ทฐํฌํŠธ ๊ธฐ๋ฐ˜ ์‹ค์‹œ๊ฐ„ ์ˆ™์†Œ ๊ฒ€์ƒ‰ ๊ตฌํ˜„  (0) 2026.01.14
'๐Ÿ’™ ํ”„๋ก ํŠธ์—”๋“œ(FE)' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€
  • FE ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ(FE ์•„ํ‚คํ…์ณ)
  • React ์ƒํƒœ๊ด€๋ฆฌ ( ์ง€์—ญ, ์ „์—ญ, ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ )
  • CSR / SSR / SSG / ISR ๋ Œ๋”๋ง ๋ฐฉ์‹ ๋น„๊ต
  • Next.js 16 + Turbopack + pnpm ๋ชจ๋…ธ๋ ˆํฌ: Docker ๋นŒ๋“œ ์™„์ „ ์ •๋ณต
์—ฐ์žŽ(lotus leaf)
์—ฐ์žŽ(lotus leaf)
  • ์—ฐ์žŽ(lotus leaf)
    lotus' s develog ๐Ÿƒ
    ์—ฐ์žŽ(lotus leaf)
  • ์ „์ฒด
    ์˜ค๋Š˜
    ์–ด์ œ
    • ๋ถ„๋ฅ˜ ์ „์ฒด๋ณด๊ธฐ (79)
      • โœ๏ธ ๊ฐœ๋ฐœํšŒ๊ณ ๋ก (5)
      • ๐Ÿงฎ ์•Œ๊ณ ๋ฆฌ์ฆ˜ (3)
      • ๐Ÿ’™ ํ”„๋ก ํŠธ์—”๋“œ(FE) (19)
        • HTML (0)
        • CSS (0)
        • Javascript (0)
        • React (0)
        • Next.js (0)
        • webpack & babel (0)
      • ๐Ÿ’ป ๋ฐฑ์—”๋“œ(BE) (2)
        • Nest.js (0)
        • Express.js (0)
        • MySQL (1)
      • โš™๏ธ ์ธํ”„๋ผ(Devops) (2)
      • ๐Ÿค– AI (1)
      • ๐ŸŒ WEB (8)
      • ๐Ÿ’ป CS (16)
        • ์ž๋ฃŒ๊ตฌ์กฐ (0)
        • ์ปดํ“จํ„ฐ ๋„คํŠธ์›Œํฌ (1)
        • ์šด์˜์ฒด์ œ (0)
        • ์ธ๊ณต์ง€๋Šฅ (8)
        • ์›น ๋ณด์•ˆ (1)
        • ํด๋ผ์šฐ๋“œ ์ปดํ“จํŒ… (6)
      • ๐Ÿ–‹๏ธ DevLog (1)
      • ๐Ÿฆพ ๋กœ๋ณดํ‹ฑ์Šค (4)
      • ๐Ÿ“— ๋„ค์ด๋ฒ„๋ถ€์ŠคํŠธ์บ ํ”„ ์›น ๋ชจ๋ฐ”์ผ (0)
      • ๐ŸŽฎ Unity(C#) (10)
      • ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด (4)
        • C (4)
        • C++ (0)
        • Java (0)
        • Python (0)
      • MSA (1)
  • ๋ธ”๋กœ๊ทธ ๋ฉ”๋‰ด

    • ํ™ˆ
    • ํƒœ๊ทธ
    • ๋ฐฉ๋ช…๋ก
  • ๋งํฌ

  • ๊ณต์ง€์‚ฌํ•ญ

  • ์ธ๊ธฐ ๊ธ€

  • ํƒœ๊ทธ

    nav2
    ์ฝ”๋”ํŒจ๋“œ
    ๋ฐฑ์ค€
    advaned detail
    c์–ธ์–ด
    ์ŠคํŒธ๋ถ„๋ฅ˜๊ธฐ
    deploy-aws
    auto-scaling
    ros workspace
    ์ŠคํŒธ๋ฉ”์ผ๋ถ„๋ฅ˜๊ธฐ
    ์กฐํ•ฉ
    ์ˆœ์—ด
    ์•Œ๊ณ ๋ฆฌ์ฆ˜
    ros bridge
    next.js12
    ํŒŒ์ผํŠธ๋ฆฌ
    C#
    turtlebot
    client-streaming
    c++
    isaac automator
    soft margin svm
    C
    AWS
    ๊ธฐ์ดˆ์•Œ๊ณ ๋ฆฌ์ฆ˜
    hard margin svm
    gaussian rbf svm
    ์ดํ™”์—ฌ์ž๋Œ€ํ•™๊ต #๋„์ „ํ•™๊ธฐ์ œ
    Devops #๋Œ€๊ทœ๋ชจํŠธ๋ž˜ํ”ฝ์ฒ˜๋ฆฌ
    ๋ฆฌ์•กํŠธ
  • ์ตœ๊ทผ ๋Œ“๊ธ€

  • ์ตœ๊ทผ ๊ธ€

  • hELLOยท Designed By์ •์ƒ์šฐ.v4.10.6
์—ฐ์žŽ(lotus leaf)
React ์„ฑ๋Šฅ ์ตœ์ ํ™”
์ƒ๋‹จ์œผ๋กœ

ํ‹ฐ์Šคํ† ๋ฆฌํˆด๋ฐ”