๐ ๋ฐฐ์ด ์ธ๋ฑ์ค๋ฅผ React ๋ฆฌ์คํธ `key`๋ก ์ฌ์ฉํ๊ธฐ: ์์ธก ๋ถ๊ฐ๋ฅํ UI์ ์ฑ๋ฅ ์ ํ
Summary
React ๋ฆฌ์คํธ์์ key
prop์ผ๋ก ๋ฐฐ์ด index
๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ๋ฆฌ์คํธ ํญ๋ชฉ์ ์์ ๋ณ๊ฒฝ, ์ถ๊ฐ, ์ญ์ ์ ์์ธก ๋ถ๊ฐ๋ฅํ UI ๋ฒ๊ทธ์ ์ฑ๋ฅ ๋ฌธ์ ๋ฅผ ์ผ๊ธฐํฉ๋๋ค. React๊ฐ ํญ๋ชฉ์ ์ ์์ ์ ํํ ์ถ์ ํ๋๋ก ๊ฐ ๋ฆฌ์คํธ ํญ๋ชฉ์ ๊ณ ์ ํ๊ณ ์์ ์ ์ธ ์๋ณ์(ID)๋ฅผ key
๋ก ์ ๊ณตํด์ผ ํฉ๋๋ค.
Why Wrong?
React๋ ๊ฐ์ DOM์ ํจ์จ์ ์ผ๋ก ์
๋ฐ์ดํธํ๊ธฐ ์ํด '์ฌ์กฐ์ (Reconciliation)' ๊ณผ์ ์ ๊ฑฐ์นฉ๋๋ค. ์ด ๊ณผ์ ์์ ๋ฆฌ์คํธ์ ๊ฐ ํญ๋ชฉ์ ์๋ณํ๊ณ , ๋ณ๊ฒฝ ์ฌํญ์ ์ถ์ ํ๋ ๋ฐ key
prop์ด ํต์ฌ์ ์ธ ์ญํ ์ ํฉ๋๋ค. key
๋ ๋ฆฌ์คํธ ๋ด์์ ๊ฐ ํญ๋ชฉ์ ๊ณ ์ ํ ์ ๋ถ์ ๋ถ์ฌํ๋ฉฐ, React๊ฐ ์ด๋ค ํญ๋ชฉ์ด ์ถ๊ฐ๋์๋์ง, ์ ๊ฑฐ๋์๋์ง, ์์๊ฐ ๋ณ๊ฒฝ๋์๋์ง๋ฅผ ํ์
ํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค.
๋ฐฐ์ด์ index
๋ฅผ key
๋ก ์ฌ์ฉํ๋ ๊ฒ์ ๋ค์๊ณผ ๊ฐ์ ์ฌ๊ฐํ ๋ฌธ์ ๋ฅผ ์ผ๊ธฐํฉ๋๋ค:
-
์์ธก ๋ถ๊ฐ๋ฅํ UI ๋ฒ๊ทธ ๋ฐ ์ํ ์ ์ค: ๋ฆฌ์คํธ์ ํญ๋ชฉ ์์๊ฐ ๋ณ๊ฒฝ๋๊ฑฐ๋, ์ค๊ฐ์ ํญ๋ชฉ์ด ์ถ๊ฐ/์ญ์ ๋ ๊ฒฝ์ฐ, ๊ธฐ์กด ํญ๋ชฉ๋ค์
index
๊ฐ ๋ฐ๋๊ฒ ๋ฉ๋๋ค. React๋ ๋ฐ๋index
๋ฅผ ๊ธฐ์ค์ผ๋ก DOM ์์๋ฅผ ์ฌ์ฌ์ฉํ๋ฏ๋ก, ์ด์ ์ ๋ ๋๋ง๋ DOM ์์์ ๋ด๋ถ ์ํ(์:<input>
ํ๋์ ๊ฐ,<checkbox>
์ ์ฒดํฌ ์ํ)๊ฐ ๋ค๋ฅธ ํญ๋ชฉ์ ์๋ชป ์ฐ๊ฒฐ๋ ์ ์์ต๋๋ค. ์ด๋ ์ฌ์ฉ์๊ฐ ์ ๋ ฅํ ๋ด์ฉ์ด ์ฌ๋ผ์ง๊ฑฐ๋, ์ฒดํฌ๋ฐ์ค๊ฐ ๋ง์๋๋ก ์ฒดํฌ/ํด์ ๋๋ ๋ฑ ์น๋ช ์ ์ธ UI ๋ฒ๊ทธ๋ก ์ด์ด์ง๋๋ค. -
๋นํจ์จ์ ์ธ ์ฑ๋ฅ:
key
๊ฐ ๋ถ์์ ํ๋ฉด React๋ ํญ๋ชฉ์ ์์๊ฐ ๋ฐ๋์์ ๋ ํด๋น ํญ๋ชฉ์ ์ฌํ์ฉํ๊ธฐ๋ณด๋ค ๊ธฐ์กด DOM ์์๋ฅผ ํ๊ดดํ๊ณ ์๋ก์ด DOM ์์๋ฅผ ์์ฑํ๋ ๊ฒฝํฅ์ด ์์ต๋๋ค. ์ด๋ ๋ถํ์ํ DOM ์กฐ์์ ์ ๋ฐํ์ฌ ๋ ๋๋ง ์ฑ๋ฅ์ ์ ํ์ํฌ ์ ์์ต๋๋ค. ํนํ ๋๊ท๋ชจ ๋ฆฌ์คํธ์์ ์ด ๋ฌธ์ ๋ ๋์ฑ ๋๋๋ฌ์ง๋๋ค. -
์ปดํฌ๋ํธ ๋ผ์ดํ์ฌ์ดํด ๋ฌธ์ :
key
๊ฐ ๋ณ๊ฒฝ๋๋ฉด React๋ ํด๋น ์ปดํฌ๋ํธ๋ฅผ ์์ ํ ์ธ๋ง์ดํธํ๊ณ ๋ค์ ๋ง์ดํธํฉ๋๋ค. ์ด๋ ๋ถํ์ํuseEffect
ํด๋ฆฐ์ ๋ฐ ์ฌ์คํ, ๊ทธ๋ฆฌ๊ณ ์ํ ์ด๊ธฐํ๋ฅผ ์ด๋ํ์ฌ ์์์น ๋ชปํ ๋ถ์์ฉ์ ์ผ์ผํฌ ์ ์์ต๋๋ค.
How to Fix?
๋ฆฌ์คํธ ๋ ๋๋ง ์์๋ ๋ฐ๋์ ๊ฐ ๋ฆฌ์คํธ ํญ๋ชฉ์ ๊ณ ์ ํ๊ณ ์์ ์ ์ธ(unique and stable) ์๋ณ์๋ฅผ key
prop์ผ๋ก ์ ๊ณตํด์ผ ํฉ๋๋ค. ๋ฐ์ดํฐ๊ฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ API์์ ์ค๋ ๊ฒฝ์ฐ, ๋๋ถ๋ถ์ ๊ฒฝ์ฐ ๊ณ ์ ํ id
ํ๋๋ฅผ ๊ฐ์ง๊ณ ์์ต๋๋ค. ์ด id
๋ฅผ key
๋ก ์ฌ์ฉํ๋ฉด ๋ฉ๋๋ค.
- ๊ณ ์ ID ์ฌ์ฉ: ๋ฐ์ดํฐ๊ฐ ์ด๋ฏธ ๊ณ ์ ํ ID๋ฅผ ๊ฐ์ง๊ณ ์๋ค๋ฉด ํด๋น ID๋ฅผ
key
๋ก ์ฌ์ฉํฉ๋๋ค. - UUID ์์ฑ: ๋ง์ฝ ๋ฐ์ดํฐ์ ๊ณ ์ ID๊ฐ ์๋ค๋ฉด,
uuid
์ ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ฌ ํด๋ผ์ด์ธํธ ์ธก์์ ๊ณ ์ ํ ID๋ฅผ ์์ฑํ์ฌkey
๋ก ๋ถ์ฌํ ์ ์์ต๋๋ค. ๋จ, ์ด ๊ฒฝ์ฐ ๋ฐ์ดํฐ๊ฐ ์๊ตฌ์ ์ผ๋ก ์ ์ฅ๋ ๋๋ ์ด ID๊ฐ ์ ์ง๋๋๋ก ๊ด๋ฆฌํด์ผ ํฉ๋๋ค. - ์ตํ์ ์๋จ (๋งค์ฐ ์ ํ์ ): ๋ฆฌ์คํธ๊ฐ ์ ๋ ๋ณ๊ฒฝ๋์ง ์์ผ๋ฉฐ(ํญ๋ชฉ ์ถ๊ฐ/์ญ์ /์ฌ์ ๋ ฌ ์์) ๊ฐ ํญ๋ชฉ์ด ๋ณธ์ง์ ์ผ๋ก ๊ณ ์ ํ ๊ฒฝ์ฐ์๋ง
index
๋ฅผkey
๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค. ๊ทธ๋ฌ๋ ์ด๋ฌํ ์ํฉ์ ์ค๋ฌด์์ ๋งค์ฐ ๋๋ฌผ๋ฉฐ, ๋๋ถ๋ถ์ ๊ฒฝ์ฐ ๋ฐ์ดํฐ๋ ๋์ ์ผ๋ก ๋ณํ ๊ฐ๋ฅ์ฑ์ด ์์ผ๋ฏ๋ก ์ ์คํด์ผ ํฉ๋๋ค.
Before Code (Bad)
import React, { useState } from 'react';
function BadKeyExample() {
const [todos, setTodos] = useState([
{ id: 1, text: '์์นจ ์์ฌ' },
{ id: 2, text: '์ฝ๋ ๋ฆฌ๋ทฐ' },
{ id: 3, text: '์ ์ฌ ์์ฌ' },
]);
const removeTodo = (indexToRemove) => {
setTodos(todos.filter((_, index) => index !== indexToRemove));
};
const addTodo = () => {
const newTodo = { id: Date.now(), text: `์๋ก์ด ํ ์ผ ${todos.length + 1}` };
setTodos([newTodo, ...todos]); // ๋งจ ์์ ์ ํ ์ผ ์ถ๊ฐ
};
return (
<div>
<h3>โ ๋ฐฐ์ด ์ธ๋ฑ์ค๋ฅผ key๋ก ์ฌ์ฉ</h3>
<button onClick={addTodo}>๋งจ ์์ ํ ์ผ ์ถ๊ฐ</button>
<ul style={{ border: '1px solid red', padding: '10px' }}>
{todos.map((todo, index) => (
<li key={index} style={{ marginBottom: '5px' }}> {/* โ ๏ธ ๋ฌธ์ ์ ์์ธ: index๋ฅผ key๋ก ์ฌ์ฉ */}
<input type="text" value={todo.text} onChange={(e) => {
const newTodos = [...todos];
newTodos[index].text = e.target.value;
setTodos(newTodos);
}} />
<button onClick={() => removeTodo(index)} style={{ marginLeft: '10px' }}>์ญ์ </button>
</li>
))}
</ul>
<p style={{ color: 'red', fontWeight: 'bold' }}>
โ ๏ธ '๋งจ ์์ ํ ์ผ ์ถ๊ฐ' ๋ฒํผ์ ๋๋ฅธ ํ, ๊ธฐ์กด '์์นจ ์์ฌ' ์นธ์ 'ํ
์คํธ'๋ผ๊ณ ์
๋ ฅํด๋ณด์ธ์. ๊ทธ๋ฆฌ๊ณ ๋ค์ '๋งจ ์์ ํ ์ผ ์ถ๊ฐ'๋ฅผ ๋๋ฅด๊ฑฐ๋ ์ค๊ฐ์ '์ฝ๋ ๋ฆฌ๋ทฐ'๋ฅผ ์ญ์ ํ๋ฉด ์
๋ ฅ๊ฐ์ด ์ํค๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.
</p>
</div>
);
}
export default BadKeyExample;
After Code (Good)
import React, { useState } from 'react';
function GoodKeyExample() {
const [todos, setTodos] = useState([
{ id: 1, text: '์์นจ ์์ฌ' },
{ id: 2, text: '์ฝ๋ ๋ฆฌ๋ทฐ' },
{ id: 3, text: '์ ์ฌ ์์ฌ' },
]);
const removeTodo = (idToRemove) => {
setTodos(todos.filter(todo => todo.id !== idToRemove));
};
const addTodo = () => {
const newTodo = { id: Date.now(), text: `์๋ก์ด ํ ์ผ ${todos.length + 1}` }; // ๊ณ ์ ID ์์ฑ
setTodos([newTodo, ...todos]); // ๋งจ ์์ ์ ํ ์ผ ์ถ๊ฐ
};
return (
<div>
<h3>โ
๊ณ ์ ํ ID๋ฅผ key๋ก ์ฌ์ฉ</h3>
<button onClick={addTodo}>๋งจ ์์ ํ ์ผ ์ถ๊ฐ</button>
<ul style={{ border: '1px solid green', padding: '10px' }}>
{todos.map((todo) => (
<li key={todo.id} style={{ marginBottom: '5px' }}> {/* โจ ํด๊ฒฐ: ๊ณ ์ ํ todo.id๋ฅผ key๋ก ์ฌ์ฉ */}
<input type="text" value={todo.text} onChange={(e) => {
const newTodos = todos.map(item =>
item.id === todo.id ? { ...item, text: e.target.value } : item
);
setTodos(newTodos);
}} />
<button onClick={() => removeTodo(todo.id)} style={{ marginLeft: '10px' }}>์ญ์ </button>
</li>
))}
</ul>
<p style={{ color: 'green', fontWeight: 'bold' }}>
โจ '๋งจ ์์ ํ ์ผ ์ถ๊ฐ' ๋ฒํผ์ ๋๋ฅธ ํ, ๊ธฐ์กด '์์นจ ์์ฌ' ์นธ์ 'ํ
์คํธ'๋ผ๊ณ ์
๋ ฅํด๋ณด์ธ์. ๋ค์ '๋งจ ์์ ํ ์ผ ์ถ๊ฐ'๋ฅผ ๋๋ฅด๊ฑฐ๋ ์ค๊ฐ์ '์ฝ๋ ๋ฆฌ๋ทฐ'๋ฅผ ์ญ์ ํด๋ ์
๋ ฅ๊ฐ์ด ์ฌ๋ฐ๋ฅด๊ฒ ์ ์ง๋ฉ๋๋ค.
</p>
</div>
);
}
export default GoodKeyExample;