React์์ ์ํ(๊ฐ์ฒด/๋ฐฐ์ด)๋ฅผ ์ง์ ์์ ํ๋ฉด ์์ ๋น๊ต๋ก ์ธํด ๋ณ๊ฒฝ์ ๊ฐ์งํ์ง ๋ชปํด UI๊ฐ ์ ๋ฐ์ดํธ๋์ง ์๊ณ ๋ฒ๊ทธ๋ฅผ ์ ๋ฐํฉ๋๋ค. ํญ์ ์คํ๋ ๋ ๋ฌธ๋ฒ ๋ฑ์ผ๋ก ์๋ก์ด ์ํ๋ฅผ ์์ฑํ์ฌ ๋ถ๋ณ์ฑ์ ์ ์งํด์ผ ํฉ๋๋ค.
React๋ฅผ ๋น๋กฏํ ๋๋ถ๋ถ์ ํ๋ ํ๋ก ํธ์๋ ํ๋ ์์ํฌ๋ ์ํ ๋ณํ๋ฅผ ๊ฐ์งํ๊ธฐ ์ํด '๋ถ๋ณ์ฑ(Immutability)' ๊ฐ๋
์ ์ค์ํ๊ฒ ์ฌ์ฉํฉ๋๋ค. ํนํ React๋ ์ํ ๊ฐ์ฒด๋ ๋ฐฐ์ด์ด ๋ณ๊ฒฝ๋์๋์ง ํ์ธํ ๋, ๋ด๋ถ ๋ฐ์ดํฐ๊ฐ ์๋ '๋ฉ๋ชจ๋ฆฌ ์ฃผ์'๋ฅผ ๊ธฐ์ค์ผ๋ก ์์ ๋น๊ต(shallow comparison)๋ฅผ ์ํํฉ๋๋ค. ๋ง์ฝ ๊ธฐ์กด ์ํ ๊ฐ์ฒด๋ ๋ฐฐ์ด์ ์ง์ ์์ (๋ฎคํ
์ด์
)ํ๋ฉด, ๋ฉ๋ชจ๋ฆฌ ์ฃผ์๋ ๊ทธ๋๋ก ์ ์ง๋๊ธฐ ๋๋ฌธ์ React๋ ์ํ๊ฐ ๋ณ๊ฒฝ๋์ง ์์๋ค๊ณ ํ๋จํ์ฌ ํด๋น ์ปดํฌ๋ํธ๋ฅผ ๋ค์ ๋ ๋๋งํ์ง ์์ต๋๋ค. ์ด๋ ์ฌ์ฉ์์๊ฒ stale(์ค๋๋) UI๋ฅผ ๋ณด์ฌ์ฃผ๊ฒ ๋๋ฉฐ, ์ค์ ์ ํ๋ฆฌ์ผ์ด์
์ํ์ ํ๋ฉด ๊ฐ์ ๋ถ์ผ์น๋ฅผ ์ด๋ํฉ๋๋ค. ๊ฒฐ๊ณผ์ ์ผ๋ก ์์ธก ๋ถ๊ฐ๋ฅํ ๋ฒ๊ทธ๊ฐ ๋ฐ์ํ๊ณ , ์์ธ์ ์ถ์ ํ๊ธฐ ๋งค์ฐ ์ด๋ ค์ด ๋๋ฒ๊น
์ง์ฅ์ ๋น ์ง๊ฒ ๋ฉ๋๋ค. ๋ํ, React.memo
๋ useMemo
์ ๊ฐ์ ์ต์ ํ ๊ธฐ๋ฒ๋ ์ ๋๋ก ์๋ํ์ง ์์ ๋ถํ์ํ ๋ ๋๋ง์ ์ ๋ฐํ๊ฑฐ๋, ๋ฐ๋๋ก ๋ ๋๋ง์ ๊ฑด๋๋ฐ๋ ๋ฑ ์ฑ๋ฅ์๋ ์
์ํฅ์ ๋ฏธ์น ์ ์์ต๋๋ค.
์ํ๋ฅผ ์
๋ฐ์ดํธํ ๋๋ ํญ์ ๊ธฐ์กด ์ํ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก '์๋ก์ด' ๊ฐ์ฒด๋ ๋ฐฐ์ด์ ์์ฑํ์ฌ ๋ถ๋ณ์ฑ์ ์ ์งํด์ผ ํฉ๋๋ค. JavaScript์ ๊ตฌ์กฐ ๋ถํด ํ ๋น(Spread syntax: ...
), map
, filter
, slice
, concat
๋ฑ์ ๋ฉ์๋๋ฅผ ํ์ฉํ์ฌ ์๋ณธ์ ๋ณ๊ฒฝํ์ง ์๋ ๋ฐฉ์์ผ๋ก ์๋ก์ด ์ํ๋ฅผ ์์ฑํฉ๋๋ค. ๋ณต์กํ๊ณ ๊น๊ฒ ์ค์ฒฉ๋ ๊ฐ์ฒด๋ ๋ฐฐ์ด์ ๊ฒฝ์ฐ, Immer.js
์ ๊ฐ์ ๋ถ๋ณ์ฑ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ฉด ๋ง์น ๊ฐ๋ณ์ ์ธ ์ฝ๋๋ฅผ ์์ฑํ๋ ๊ฒ์ฒ๋ผ ํธ๋ฆฌํ๊ฒ ๋ถ๋ณ์ฑ์ ์ ์งํ ์ ์์ต๋๋ค. ์ด๋ ์ฝ๋์ ๊ฐ๋
์ฑ์ ๋์ด๊ณ ์ค์๋ก ์ธํ ๋ฎคํ
์ด์
์ ๋ฐฉ์งํ์ฌ ์์ธก ๊ฐ๋ฅํ ์ํ ๊ด๋ฆฌ๋ฅผ ๊ฐ๋ฅํ๊ฒ ํฉ๋๋ค.
import React, { useState } from 'react';
function BuggyList() {
const [items, setItems] = useState([ { id: 1, text: '์ฒซ ๋ฒ์งธ ์์ดํ
', completed: false } ]);
const handleAddItem = () => {
// โ ์ง์ ๋ฐฐ์ด์ ๋ณ๊ฒฝ (๋ฎคํ
์ด์
)
items.push({ id: items.length + 1, text: `์์ดํ
${items.length + 1}`, completed: false });
setItems(items); // React๋ ์ด ๋ณ๊ฒฝ์ ๊ฐ์งํ์ง ๋ชปํ ์ ์์ (์ฐธ์กฐ๊ฐ ๋์ผ)
};
const handleToggleComplete = (id) => {
// โ ์ง์ ๊ฐ์ฒด ์์ฑ์ ๋ณ๊ฒฝ (๋ฎคํ
์ด์
)
const itemToUpdate = items.find(item => item.id === id);
if (itemToUpdate) {
itemToUpdate.completed = !itemToUpdate.completed;
setItems(items); // React๋ ์ด ๋ณ๊ฒฝ์ ๊ฐ์งํ์ง ๋ชปํ ์ ์์ (์ฐธ์กฐ๊ฐ ๋์ผ)
}
};
return (
<div>
<h2>๋ฒ๊ทธ๊ฐ ์๋ ToDo ๋ฆฌ์คํธ</h2>
<button onClick={handleAddItem}>์์ดํ
์ถ๊ฐ</button>
<ul>
{items.map(item => (
<li key={item.id} style={{ textDecoration: item.completed ? 'line-through' : 'none' }}>
{item.text}
<input
type="checkbox"
checked={item.completed}
onChange={() => handleToggleComplete(item.id)}
/>
</li>
))}
</ul>
</div>
);
}
export default BuggyList;
import React, { useState } => {
const [items, setItems] = useState([ { id: 1, text: '์ฒซ ๋ฒ์งธ ์์ดํ
', completed: false } ]);
const handleAddItem = () => {
// โ
์คํ๋ ๋ ๋ฌธ๋ฒ์ผ๋ก ์๋ก์ด ๋ฐฐ์ด ์์ฑ
setItems(prevItems => [
...prevItems,
{ id: prevItems.length + 1, text: `์์ดํ
${prevItems.length + 1}`, completed: false }
]);
};
const handleToggleComplete = (id) => {
// โ
map์ ์ฌ์ฉํ์ฌ ์๋ก์ด ๋ฐฐ์ด๊ณผ ์๋ก์ด ๊ฐ์ฒด ์์ฑ
setItems(prevItems =>
prevItems.map(item =>
item.id === id ? { ...item, completed: !item.completed } : item
)
);
};
return (
<div>
<h2>๋ถ๋ณ์ฑ์ ์งํจ ToDo ๋ฆฌ์คํธ</h2>
<button onClick={handleAddItem}>์์ดํ
์ถ๊ฐ</button>
<ul>
{items.map(item => (
<li key={item.id} style={{ textDecoration: item.completed ? 'line-through' : 'none' }}>
{item.text}
<input
type="checkbox"
checked={item.completed}
onChange={() => handleToggleComplete(item.id)}
/>
</li>
))}
</ul>
</div>
);
}
export default FixedList;