ระวังเมื่อวนซ้ำอาร์เรย์!
เป็นความเข้าใจผิดที่พบบ่อยว่าการใช้ดัชนีขององค์ประกอบในอาร์เรย์เป็นวิธีที่ยอมรับได้ในการระงับข้อผิดพลาดที่คุณอาจคุ้นเคยกับ:
Each child in an array should have a unique "key" prop.
อย่างไรก็ตามในหลายกรณีมันไม่ได้เป็น! นี่คือการต่อต้านรูปแบบที่สามารถในบางสถานการณ์ที่นำไปสู่พฤติกรรมที่ไม่พึงประสงค์
ทำความเข้าใจกับkey
เสา
ตอบสนองการใช้งานkey
เสาที่จะเข้าใจความสัมพันธ์ธาตุองค์ประกอบเพื่อ DOM ซึ่งถูกนำมาใช้สำหรับกระบวนการปรองดอง ดังนั้นจึงเป็นเรื่องสำคัญมากที่ที่สำคัญมักจะยังคงอยู่ที่ไม่ซ้ำกันมิฉะนั้นมีโอกาสที่ดีที่จะ React ผสมขึ้นองค์ประกอบและกลายพันธุ์ที่ไม่ถูกต้องอย่างใดอย่างหนึ่ง นอกจากนี้ยังเป็นสิ่งสำคัญที่ปุ่มเหล่านี้จะคงที่ตลอดทั้งการแสดงผลใหม่เพื่อรักษาประสิทธิภาพที่ดีที่สุด
ที่ถูกกล่าวว่าก็ไม่เสมอจำเป็นต้องใช้ข้างต้นให้เป็นที่รู้จักกันว่าอาร์เรย์เป็นแบบคงที่สมบูรณ์ อย่างไรก็ตามสนับสนุนให้มีการใช้แนวทางปฏิบัติที่ดีที่สุดเท่าที่ทำได้
ผู้พัฒนา React กล่าวในปัญหา GitHub นี้ :
- คีย์ไม่ได้เกี่ยวกับประสิทธิภาพ แต่เกี่ยวกับตัวตน (ซึ่งจะนำไปสู่ประสิทธิภาพที่ดีขึ้น) ค่าที่กำหนดแบบสุ่มและการเปลี่ยนแปลงไม่ใช่ตัวตน
- เราไม่สามารถให้ปุ่ม [โดยอัตโนมัติ] แนบเนียนโดยไม่ทราบว่าข้อมูลของคุณเป็นแบบจำลองอย่างไร ฉันอยากจะแนะนำให้ใช้ฟังก์ชั่นแฮชบางอย่างถ้าคุณไม่มีรหัส
- เรามีคีย์ภายในอยู่แล้วเมื่อเราใช้อาร์เรย์ แต่มันคือดัชนีในอาร์เรย์ เมื่อคุณแทรกองค์ประกอบใหม่กุญแจเหล่านั้นผิด
ในระยะสั้นkey
ควรเป็น:
- ที่ไม่ซ้ำกัน - ที่สำคัญไม่สามารถจะเหมือนกันกับที่ขององค์ประกอบพี่น้อง
- คงที่ - กุญแจไม่ควรเปลี่ยนระหว่างเรนเดอร์
การใช้key
อุปกรณ์ประกอบฉาก
ตามคำอธิบายข้างต้นศึกษาตัวอย่างต่อไปนี้อย่างละเอียดและพยายามนำไปใช้เมื่อเป็นไปได้แนวทางที่แนะนำ
ไม่ดี (อาจเกิดขึ้น)
<tbody>
{rows.map((row, i) => {
return <ObjectRow key={i} />;
})}
</tbody>
นี่เป็นความผิดพลาดที่พบบ่อยที่สุดเมื่อวนซ้ำแถวลำดับใน React วิธีการนี้ไม่ได้"ผิดปกติ"แต่เป็นเพียง"อันตราย"หากคุณไม่รู้ว่าคุณกำลังทำอะไรอยู่ หากคุณวนซ้ำผ่านอาร์เรย์คงที่นี่เป็นวิธีที่ถูกต้องสมบูรณ์ (เช่นอาร์เรย์ของลิงก์ในเมนูนำทางของคุณ) อย่างไรก็ตามหากคุณเพิ่มลบจัดลำดับใหม่หรือกรองรายการคุณต้องระมัดระวัง ดูรายละเอียดคำอธิบายนี้ในเอกสารอย่างเป็นทางการ
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>
ในตัวอย่างนี้เราใช้อาเรย์แบบไม่คงที่และเราไม่ได้ จำกัด ตัวเองให้ใช้มันเป็นสแต็ก นี่เป็นวิธีที่ไม่ปลอดภัย (คุณจะเห็นสาเหตุ) สังเกตวิธีที่เราเพิ่มรายการไปยังจุดเริ่มต้นของอาเรย์ (โดยทั่วไปจะไม่เปลี่ยน) ค่าสำหรับแต่ละรายการ<input>
จะยังคงอยู่ ทำไม? เนื่องจากkey
ไม่ได้ระบุแต่ละรายการโดยไม่ซ้ำกัน
ในคำอื่น ๆ ในตอนแรกมีItem 1
key={0}
เมื่อเราเพิ่มรายการที่สองรายการด้านบนจะกลายเป็นItem 2
ตามด้วยItem 1
เป็นรายการที่สอง อย่างไรก็ตามตอนนี้Item 1
มีkey={1}
และไม่ได้key={0}
อีกต่อไป แต่Item 2
ตอนนี้มีkey={0}
!!
ดังนั้น React จึงคิดว่า<input>
องค์ประกอบต่าง ๆ ไม่ได้เปลี่ยนไปเพราะItem
ปุ่ม with 0
อยู่ด้านบนเสมอ!
เหตุใดบางครั้งวิธีนี้จึงไม่ดีเท่านั้น
วิธีการนี้มีความเสี่ยงเฉพาะเมื่ออาร์เรย์ถูกกรองจัดเรียงใหม่หรือรายการจะถูกเพิ่ม / ลบ หากเป็นแบบคงที่ตลอดเวลาแสดงว่าปลอดภัยอย่างสมบูรณ์ที่จะใช้ ตัวอย่างเช่นเมนูการนำทางเช่น["Home", "Products", "Contact us"]
สามารถทำซ้ำได้อย่างปลอดภัยด้วยวิธีนี้เพราะคุณอาจไม่เคยเพิ่มลิงก์ใหม่หรือจัดเรียงใหม่
ในระยะสั้นนี่คือเมื่อคุณสามารถใช้ดัชนีอย่างปลอดภัยเป็นkey
:
- อาร์เรย์คงที่และจะไม่เปลี่ยนแปลง
- อาร์เรย์จะไม่ถูกกรอง (แสดงชุดย่อยของอาร์เรย์)
- ไม่เรียงลำดับใหม่
- อาร์เรย์ใช้เป็นสแต็กหรือ LIFO (เข้าก่อนออกก่อน) กล่าวอีกนัยหนึ่งการเพิ่มสามารถทำได้ในตอนท้ายของอาร์เรย์ (เช่นการพุช) และสามารถลบรายการสุดท้ายเท่านั้น (เช่นป๊อป)
หากเราในตัวอย่างด้านบนผลักรายการเพิ่มไปยังจุดสิ้นสุดของอาร์เรย์ลำดับสำหรับรายการที่มีอยู่แต่ละรายการจะถูกต้องเสมอ
ที่เลวร้ายมาก
<tbody>
{rows.map((row) => {
return <ObjectRow key={Math.random()} />;
})}
</tbody>
ในขณะที่วิธีการนี้อาจจะรับประกันไม่ซ้ำกันของคีย์ก็จะมักจะแรงตอบสนองต่ออีกครั้งทำให้รายการในแต่ละรายการแม้ในขณะนี้ไม่จำเป็นต้อง นี่เป็นวิธีแก้ปัญหาที่แย่มากเพราะส่งผลกระทบต่อประสิทธิภาพอย่างมาก ไม่ต้องพูดถึงว่าไม่มีใครสามารถแยกความเป็นไปได้ของการชนกันของข้อมูลที่สำคัญในเหตุการณ์ที่Math.random()
สร้างหมายเลขเดียวกันสองครั้ง
คีย์ที่ไม่เสถียร (เช่นที่สร้างโดยMath.random()
) จะทำให้อินสแตนซ์ของคอมโพเนนต์จำนวนมากและโหนด DOM ถูกสร้างขึ้นใหม่โดยไม่จำเป็นซึ่งอาจทำให้ประสิทธิภาพการทำงานลดลงและสถานะที่หายไปในคอมโพเนนต์ย่อย
ดีมาก
<tbody>
{rows.map((row) => {
return <ObjectRow key={row.uniqueId} />;
})}
</tbody>
นี่เป็นวิธีที่ดีที่สุดเนื่องจากใช้คุณสมบัติที่ไม่ซ้ำกันสำหรับแต่ละรายการในชุดข้อมูล ตัวอย่างเช่นหากrows
มีข้อมูลที่ดึงมาจากฐานข้อมูลหนึ่งสามารถใช้คีย์หลักของตาราง ( ซึ่งมักจะเป็นตัวเลขที่เพิ่มขึ้นโดยอัตโนมัติ )
วิธีที่ดีที่สุดในการเลือกคีย์คือการใช้สตริงที่ระบุรายการในหมู่พี่น้องอย่างไม่ซ้ำกัน ส่วนใหญ่คุณจะใช้ ID จากข้อมูลของคุณเป็นคีย์
ดี
componentWillMount() {
let rows = this.props.rows.map(item => {
return {uid: SomeLibrary.generateUniqueID(), value: item};
});
}
...
<tbody>
{rows.map((row) => {
return <ObjectRow key={row.uid} />;
})}
</tbody>
นี่เป็นวิธีการที่ดีเช่นกัน หากชุดข้อมูลของคุณไม่มีข้อมูลใด ๆ ที่รับประกันความเป็นเอกลักษณ์ ( เช่นอาร์เรย์ของตัวเลขที่กำหนดเอง ) โอกาสในการชนกันของคีย์ ในกรณีเช่นนี้เป็นการดีที่สุดที่จะสร้างตัวระบุที่ไม่ซ้ำกันสำหรับแต่ละรายการในชุดข้อมูลก่อนที่จะวนซ้ำ โดยเฉพาะอย่างยิ่งเมื่อติดตั้งส่วนประกอบหรือเมื่อได้รับชุดข้อมูล ( เช่นจากprops
หรือจากการเรียกใช้ async API ) เพื่อทำสิ่งนี้เพียงครั้งเดียวและไม่ได้รับการแสดงผลอีกครั้งในแต่ละองค์ประกอบ มีห้องสมุดอยู่ไม่กี่แห่งที่สามารถมอบกุญแจให้คุณได้ นี่คือตัวอย่างหนึ่ง: ตอบสนองที่สำคัญดัชนี
key
คุณสมบัติที่ไม่ซ้ำกัน มันจะช่วย ReactJS ในการค้นหาการอ้างอิงไปยังโหนด DOM ที่เหมาะสมและอัปเดตเฉพาะเนื้อหาภายในการมาร์กอัป แต่ไม่แสดงทั้งตาราง / แถวอีกครั้ง