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
- Child ์ปดํฌ๋ํธ์ ๋ถํ์ํ Re-rendering ํ์ธ
- Child ์ปดํฌ๋ํธ์ React.memo ์ ์ฉ
- 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 ํ์
→ ๋น๋๊ธฐ ์์ ์ด ๋๋ ๋๊น์ง ๋ถ์์ ํ ์ํ๋ฅผ ๋ณด์ฌ์ฃผ์ง ์์
- ์ ํ์ ์ค๋จ ๊ฐ๋ฅํจ (Interruptible)
- ์ธ์ ์ฌ์ฉํ์ง?
- ๋ค๋ฅธ 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 |