Basic Example
Giả sử cho một component render ra danh sách các loại trái cây dựa trên mảng gồm 5 phần tử.
function Fruits() {
const [fruits, setFruits] = useState(["🍎", "🍌", "🍇", "🍋", "🍉"])
function handleClick() {
setFruits(fruits.filter((fruit) => fruit !== "🍇"))
}
return (
<>
<ul>
{fruits.map((fruit, index) => (
<li key={index}>{fruit}</li>
))}
</ul>
<button onClick={handleClick}>Remove Grape</button>
</>
)
}Có thể thấy, các thẻ <li> có key là index của từng phần tử trong mảng. Danh sách được render ra sẽ có dạng như sau:
<ul>
<li key="0">🍎</li>
<li key="1">🍌</li>
<li key="2">🍇</li>
<li key="3">🍋</li>
<li key="4">🍉</li>
</ul>Khi bấm nút “Remove Grape”, trái nho (có key là 2) sẽ bị xóa ra khỏi mảng và do đó danh sách sẽ trở thành:
<ul>
<li key="0">🍎</li>
<li key="1">🍌</li>
<li key="2">🍋</li>
<li key="3">🍉</li>
</ul>Problem
Lúc này, React sẽ thực hiện so sánh nội dung của các element có cùng key và thực hiện các thao tác sau ở trên DOM:
- Re-render element có key là
2bởi vì có sự thay đổi nội dung từ🍇sang🍋. - Re-render element có key là
3bởi vì có sự thay đổi nội dung từ🍋sang🍉.
Conclusion
Có thể thấy, việc sử dụng key là index không hề giúp giảm số lần thao tác trên DOM so với khi không sử dụng key.
A More Realistic Example
Xét một ví dụ khác thực tế hơn:
import { useState } from "react"
const initialList = ["Learn React", "Learn GraphQL"]
function ListWithUnstableIndex() {
const [list, setList] = useState(initialList)
const handleClick = (event) => {
setList(list.slice().reverse())
}
return (
<div>
<ul>
{list.map((item, index) => (
<li key={index}>
<label>{item}</label>
</li>
))}
</ul>
<button type="button" onClick={handleClick}>
Reverse List
</button>
</div>
)
}Trong đoạn code trên, các thẻ <li> sử dụng key có giá trị là index của từng phần tử trong mảng. Khi “Reverse List” được bấm, danh sách các phần tử sẽ bị đảo ngược và kết quả hiển thị ra giao diện là đúng như những gì ta mong đợi.
Problem
Vấn đề sẽ xảy ra nếu như ta thêm vào một uncontrolled element, chẳng hạn như thẻ <input>:
<ul>
{list.map((item, index) => (
<li key={index}>
<label>
<input type="checkbox" />
{item}
</label>
</li>
))}
</ul>Lúc này, nếu ta tick vào một trong hai checkbox và bấm nút “Reverse List”, các checkbox sẽ giữ nguyên vị trí và không đổi:
// the initially rendered list of items
[x] Learn React
[ ] Learn GraphQL
// becomes this after the reverse button click
[x] Learn GraphQL
[ ] Learn ReactExplanation
Lý do là vì, do các element <li> có key không đổi nên React sẽ không re-render các checkbox mà thay vào đó chỉ thay đổi dữ liệu truyền vào (biến item).
// the initially rendered list of items
[x] Learn React (index = 1)
[ ] Learn GraphQL (index = 2)
// becomes this after the reverse button click
[x] Learn GraphQL (index = 1)
[ ] Learn React (index = 2)Check
Ta kết luận được rằng: không nên dùng index của các phần tử trong mảng làm key cũng như không nên tạo key trong quá trình render (chẳng hạn như
key={Math.random()}).
Solution
Có thể giải quyết vấn đề này như bằng cách tạo ra unique id cho từng phần tử trong mảng:
const initialList = [
{ id: "a", name: "Learn React" },
{ id: "b", name: "Learn GraphQL" },
]Và sử dụng các id đó làm key:
<ul>
{list.map((item) => (
<li key={item.id}>
<label>
<input type="checkbox" />
{item.name}
</label>
</li>
))}
</ul>Các key này stable hơn do gắn liền với từng phần tử.
Tip
Ta cũng có thể sử dụng giá trị của key là:
- Dữ liệu từ database.
- Các ID được sinh ngẫu nhiên (package uuid) hoặc tăng dần.