Hãy cẩn thận khi lặp qua mảng !!
Một quan niệm sai lầm phổ biến rằng sử dụng chỉ mục của phần tử trong mảng là một cách có thể chấp nhận để loại bỏ lỗi mà bạn có thể quen thuộc:
Each child in an array should have a unique "key" prop.
Tuy nhiên, trong nhiều trường hợp thì không! Đây là mô hình chống có thể trong một số tình huống dẫn đến hành vi không mong muốn .
Hiểu key
prop
React sử dụng key
prop để hiểu mối quan hệ Thành phần-thành phần DOM, sau đó được sử dụng cho quá trình đối chiếu . Do đó, nó là rất quan trọng rằng chìa khóa luôn vẫn là độc đáo , nếu không có một cơ hội tốt Phản ứng sẽ trộn lẫn các yếu tố và đột biến một không chính xác. Điều quan trọng nữa là những phím này vẫn tĩnh trong tất cả các kết xuất lại để duy trì hiệu suất tốt nhất.
Điều đó đang được nói, không phải lúc nào người ta cũng cần phải áp dụng những điều trên, với điều kiện được biết rằng mảng hoàn toàn tĩnh. Tuy nhiên, áp dụng thực hành tốt nhất được khuyến khích bất cứ khi nào có thể.
Một nhà phát triển React đã nói trong vấn đề GitHub này :
- chìa khóa không thực sự là về hiệu suất, nó là về bản sắc (điều này dẫn đến hiệu suất tốt hơn). giá trị được gán ngẫu nhiên và thay đổi không phải là danh tính
- Chúng tôi không thể thực sự cung cấp khóa [tự động] mà không biết dữ liệu của bạn được mô hình hóa như thế nào. Tôi sẽ đề nghị có thể sử dụng một số loại hàm băm nếu bạn không có id
- Chúng tôi đã có các khóa nội bộ khi chúng tôi sử dụng mảng, nhưng chúng là chỉ mục trong mảng. Khi bạn chèn một phần tử mới, các phím đó là sai.
Nói tóm lại, key
nên là:
- Duy nhất - Khóa không thể giống với khóa của thành phần anh chị em .
- Tĩnh - Một khóa không bao giờ thay đổi giữa các kết xuất.
Sử dụng key
chống đỡ
Theo giải thích ở trên, hãy nghiên cứu kỹ các mẫu sau và cố gắng thực hiện, khi có thể, phương pháp được khuyến nghị.
Xấu (Có khả năng)
<tbody>
{rows.map((row, i) => {
return <ObjectRow key={i} />;
})}
</tbody>
Đây được cho là lỗi phổ biến nhất được thấy khi lặp qua một mảng trong React. Cách tiếp cận này không "sai" về mặt kỹ thuật , nó chỉ ... "nguy hiểm" nếu bạn không biết mình đang làm gì. Nếu bạn đang lặp qua một mảng tĩnh thì đây là một cách tiếp cận hoàn toàn hợp lệ (ví dụ: một mảng các liên kết trong menu điều hướng của bạn). Tuy nhiên, nếu bạn đang thêm, xóa, sắp xếp lại hoặc lọc các mục, thì bạn cần phải cẩn thận. Hãy xem giải thích chi tiết này trong tài liệu chính thức.
class MyApp extends React.Component {
constructor() {
super();
this.state = {
arr: ["Item 1"]
}
}
click = () => {
this.setState({
arr: ['Item ' + (this.state.arr.length+1)].concat(this.state.arr),
});
}
render() {
return(
<div>
<button onClick={this.click}>Add</button>
<ul>
{this.state.arr.map(
(item, i) => <Item key={i} text={"Item " + i}>{item + " "}</Item>
)}
</ul>
</div>
);
}
}
const Item = (props) => {
return (
<li>
<label>{props.children}</label>
<input value={props.text} />
</li>
);
}
ReactDOM.render(<MyApp />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
Trong đoạn mã này, chúng tôi đang sử dụng một mảng không tĩnh và chúng tôi không hạn chế sử dụng nó như một ngăn xếp. Đây là một cách tiếp cận không an toàn (bạn sẽ thấy tại sao). Lưu ý làm thế nào khi chúng ta thêm các mục vào đầu mảng (về cơ bản là không dịch chuyển), giá trị cho mỗi mục <input>
vẫn giữ nguyên. Tại sao? Bởi vì key
không xác định duy nhất từng mục.
Nói cách khác, lúc đầu Item 1
có key={0}
. Khi chúng ta thêm mục thứ hai, mục trên cùng trở thành Item 2
, tiếp theo Item 1
là mục thứ hai. Tuy nhiên, bây giờ Item 1
có key={1}
và không key={0}
còn nữa. Thay vào đó, Item 2
bây giờ có key={0}
!!
Như vậy, React nghĩ rằng các <input>
yếu tố không thay đổi, bởi vì Item
phím with 0
luôn ở trên cùng!
Vậy tại sao cách tiếp cận này chỉ đôi khi xấu?
Cách tiếp cận này chỉ có rủi ro nếu mảng được lọc, sắp xếp lại hoặc các mục được thêm / xóa bằng cách nào đó. Nếu nó luôn tĩnh, thì nó hoàn toàn an toàn để sử dụng. Ví dụ: một menu điều hướng như ["Home", "Products", "Contact us"]
có thể được lặp lại một cách an toàn bằng phương pháp này bởi vì có lẽ bạn sẽ không bao giờ thêm các liên kết mới hoặc sắp xếp lại chúng.
Nói tóm lại, đây là khi bạn có thể sử dụng chỉ mục một cách an toàn như key
sau:
- Các mảng là tĩnh và sẽ không bao giờ thay đổi.
- Mảng không bao giờ được lọc (hiển thị một tập hợp con của mảng).
- Các mảng không bao giờ được sắp xếp lại.
- Mảng được sử dụng như một ngăn xếp hoặc LIFO (cuối cùng vào trước ra trước). Nói cách khác, việc thêm chỉ có thể được thực hiện ở cuối mảng (tức là đẩy) và chỉ có thể xóa mục cuối cùng (tức là pop).
Thay vào đó, trong đoạn trích ở trên, đã đẩy mục được thêm vào cuối mảng, thứ tự cho mỗi mục hiện có sẽ luôn chính xác.
Rất tệ
<tbody>
{rows.map((row) => {
return <ObjectRow key={Math.random()} />;
})}
</tbody>
Mặc dù cách tiếp cận này có thể sẽ đảm bảo tính duy nhất của các phím, nhưng nó sẽ luôn buộc phản ứng để kết xuất lại từng mục trong danh sách, ngay cả khi điều này không bắt buộc. Đây là một giải pháp rất xấu vì nó ảnh hưởng lớn đến hiệu suất. Chưa kể rằng người ta không thể loại trừ khả năng xảy ra va chạm quan trọng trong trường hợp Math.random()
tạo ra cùng một số hai lần.
Các khóa không ổn định (như các khóa được tạo bởi Math.random()
) sẽ khiến nhiều trường hợp thành phần và các nút DOM được tạo lại một cách không cần thiết, điều này có thể làm giảm hiệu suất và mất trạng thái trong các thành phần con.
Rất tốt
<tbody>
{rows.map((row) => {
return <ObjectRow key={row.uniqueId} />;
})}
</tbody>
Đây được cho là cách tiếp cận tốt nhất bởi vì nó sử dụng một thuộc tính duy nhất cho mỗi mục trong bộ dữ liệu. Ví dụ: nếu rows
chứa dữ liệu được tìm nạp từ cơ sở dữ liệu, người ta có thể sử dụng Khóa chính của bảng ( thường là số tăng tự động ).
Cách tốt nhất để chọn khóa là sử dụng một chuỗi xác định duy nhất một mục danh sách giữa các anh chị em của nó. Thông thường, bạn sẽ sử dụng ID từ dữ liệu của mình làm khóa
Tốt
componentWillMount() {
let rows = this.props.rows.map(item => {
return {uid: SomeLibrary.generateUniqueID(), value: item};
});
}
...
<tbody>
{rows.map((row) => {
return <ObjectRow key={row.uid} />;
})}
</tbody>
Đây cũng là một cách tiếp cận tốt. Nếu tập dữ liệu của bạn không chứa bất kỳ dữ liệu nào đảm bảo tính duy nhất ( ví dụ: một mảng các số tùy ý ), có khả năng xảy ra xung đột chính. Trong các trường hợp như vậy, tốt nhất là tạo thủ công một mã định danh duy nhất cho từng mục trong bộ dữ liệu trước khi lặp qua nó. Tốt nhất là khi gắn thành phần hoặc khi nhận được tập dữ liệu ( ví dụ: từ props
hoặc từ lệnh gọi API không đồng bộ ), để chỉ thực hiện việc này một lần chứ không phải mỗi lần thành phần hiển thị lại. Hiện đã có một số thư viện có thể cung cấp cho bạn các khóa như vậy. Đây là một ví dụ: Reac-key-index .
key
. Nó sẽ giúp ReactJS tìm các tham chiếu đến các nút DOM thích hợp và chỉ cập nhật nội dung bên trong đánh dấu nhưng không hiển thị lại toàn bộ bảng / hàng.