การสร้างตารางที่จัดเรียงได้ด้วย React

เผยแพร่แล้ว: 2022-03-10
สรุปอย่างรวดเร็ว ↬ การ ทำให้ตารางของคุณจัดเรียงใน React อาจดูเหมือนเป็นงานที่น่ากลัว แต่ก็ไม่จำเป็นต้องยากเกินไป ในบทความนี้ เราจะนำทุกสิ่งที่คุณต้องการมาใช้เพื่อจัดการความต้องการในการจัดเรียงตารางของคุณทั้งหมด

การจัดเรียงตารางเป็นปัญหาที่ยากมากที่จะทำให้ถูกต้อง มีการโต้ตอบมากมายให้ติดตาม การกลายพันธุ์ของ DOM ที่ต้องทำมากมาย และแม้แต่อัลกอริธึมการเรียงลำดับที่ซับซ้อนด้วย เป็นเพียงหนึ่งในความท้าทายที่ยากจะแก้ไข ใช่ไหม?

แทนที่จะดึงไลบรารีภายนอก มาลองทำสิ่งต่างๆ ด้วยตัวเอง ในบทความนี้ เราจะสร้างวิธีการจัดเรียงข้อมูลแบบตารางของคุณใน React แบบใช้ซ้ำได้ เราจะลงรายละเอียดแต่ละขั้นตอนอย่างละเอียด และเรียนรู้เทคนิคที่มีประโยชน์มากมายไปพร้อมกัน

เราจะไม่พูดถึงรูปแบบพื้นฐานของ React หรือ JavaScript แต่คุณไม่จำเป็นต้องเป็นผู้เชี่ยวชาญใน React เพื่อติดตาม

การสร้างตารางด้วย React

ขั้นแรก มาสร้างส่วนประกอบตารางตัวอย่างกัน จะยอมรับอาร์เรย์ของผลิตภัณฑ์และส่งออกตารางพื้นฐานโดยแสดงรายการแถวต่อผลิตภัณฑ์

 function ProductTable(props) { const { products } = props; return ( <table> <caption>Our products</caption> <thead> <tr> <th>Name</th> <th>Price</th> <th>In Stock</th> </tr> </thead> <tbody> {products.map(product => ( <tr key={product.id}> <td>{product.name}</td> <td>{product.price}</td> <td>{product.stock}</td> </tr> ))} </tbody> </table> ); }
เพิ่มเติมหลังกระโดด! อ่านต่อด้านล่าง↓

ที่นี่ เรายอมรับผลิตภัณฑ์หลายประเภทและรวมไว้ในตารางของเรา มันคงที่และไม่สามารถจัดเรียงได้ในขณะนี้ แต่ตอนนี้ก็ใช้ได้

การเรียงลำดับข้อมูล

หากคุณเชื่อว่าผู้สัมภาษณ์ไวท์บอร์ดทั้งหมด คุณคิดว่าการพัฒนาซอฟต์แวร์เป็นอัลกอริธึมการเรียงลำดับเกือบทั้งหมด โชคดีที่เราจะไม่ดูการเรียงลำดับอย่างรวดเร็วหรือการเรียงลำดับฟองที่นี่

การเรียงลำดับข้อมูลใน JavaScript ค่อนข้างตรงไปตรงมา ต้องขอบคุณฟังก์ชันอาร์เรย์ใน sort() มันจะจัดเรียงอาร์เรย์ของตัวเลขและสตริงโดยไม่มีอาร์กิวเมนต์เพิ่มเติม:

 const array = ['mozzarella', 'gouda', 'cheddar']; array.sort(); console.log(array); // ['cheddar', 'gouda', 'mozzarella']

หากคุณต้องการสิ่งที่ฉลาดกว่านี้อีกเล็กน้อย คุณสามารถส่งต่อฟังก์ชันการจัดเรียงได้ ฟังก์ชันนี้มีให้สองรายการในรายการเป็นอาร์กิวเมนต์ และจะวางรายการหนึ่งไว้ข้างหน้าอีกรายการตามสิ่งที่คุณตัดสินใจ

เริ่มต้นด้วยการเรียงลำดับข้อมูลที่เราได้รับตามตัวอักษรตามชื่อ

 function ProductTable(props) { const { products } = props; let sortedProducts = [...products]; sortedProducts.sort((a, b) => { if (a.name < b.name) { return -1; } if (a.name > b.name) { return 1; } return 0; }); return ( <Table> {/* as before */} </Table> ); }

เกิดอะไรขึ้นที่นี่? ขั้นแรก เราสร้างสำเนาของพร็อพผลิตภัณฑ์ ซึ่งเราสามารถเปลี่ยนแปลงและเปลี่ยนแปลงได้ตามต้องการ เราจำเป็นต้องทำเช่นนี้เพราะฟังก์ชัน Array.prototype.sort จะเปลี่ยนอาร์เรย์เดิมแทนที่จะส่งคืนสำเนาที่จัดเรียงใหม่

ต่อไป เราเรียก sortedProducts.sort และส่งต่อฟังก์ชัน sorting เราตรวจสอบว่าคุณสมบัติของ name ของอาร์กิวเมนต์แรก a อยู่ก่อนอาร์กิวเมนต์ที่สอง b หรือไม่ และถ้าเป็นเช่นนั้น ให้คืนค่าเป็นลบ สิ่งนี้บ่งชี้ว่า a ควรมาก่อน b ในรายการ ถ้าชื่ออาร์กิวเมนต์แรกอยู่หลังชื่ออาร์กิวเมนต์ที่สอง เราจะคืนค่าเป็นจำนวนบวก ซึ่งบ่งชี้ว่าเราควรวาง b ก่อน a หากทั้งสองมีค่าเท่ากัน (กล่าวคือ ทั้งคู่มีชื่อเหมือนกัน) เราจะคืนค่า 0 เพื่อรักษาลำดับ

ทำให้ตารางของเราจัดเรียงได้

ตอนนี้เราสามารถตรวจสอบให้แน่ใจว่าตารางถูกจัดเรียงตามชื่อแล้ว — แต่เราจะเปลี่ยนลำดับการจัดเรียงด้วยตัวเองได้อย่างไร?

หากต้องการเปลี่ยนฟิลด์ที่เราจัดเรียง เราต้องจำฟิลด์ที่จัดเรียงอยู่ในปัจจุบัน เราจะทำอย่างนั้นด้วย useState hook

hook เป็นฟังก์ชันพิเศษที่ช่วยให้เรา "เชื่อมต่อ" กับฟังก์ชันหลักบางอย่างของ React เช่น การจัดการสถานะและทริกเกอร์ผลข้างเคียง ตะขอพิเศษนี้ช่วยให้เรารักษาสถานะภายในในส่วนประกอบของเรา และเปลี่ยนแปลงได้หากต้องการ นี่คือสิ่งที่เราจะเพิ่ม:

 const [sortedField, setSortedField] = React.useState(null);

เราเริ่มต้นด้วยการไม่เรียงลำดับอะไรเลย ต่อไป ให้เปลี่ยนส่วนหัวของตารางเพื่อรวมวิธีการเปลี่ยนเขตข้อมูลที่เราต้องการจัดเรียง

 const ProductsTable = (props) => { const { products } = props; const [sortedField, setSortedField] = React.useState(null); return ( <table> <thead> <tr> <th> <button type="button" onClick={() => setSortedField('name')}> Name </button> </th> <th> <button type="button" onClick={() => setSortedField('price')}> Price </button> </th> <th> <button type="button" onClick={() => setSortedField('stock')}> In Stock </button> </th> </tr> </thead> {/* As before */} </table> ); };

ตอนนี้ เมื่อใดก็ตามที่เราคลิกส่วนหัวของตาราง เราจะอัปเดตฟิลด์ที่เราต้องการจัดเรียงตาม เรียบร้อย-โอ้!

เรายังไม่ได้ทำการจัดเรียงจริง ๆ ดังนั้นเรามาแก้ไขกัน จำอัลกอริธึมการเรียงลำดับจากเมื่อก่อนได้หรือไม่? นี่คือการเปลี่ยนแปลงเพียงเล็กน้อยเพื่อให้ทำงานกับชื่อฟิลด์ของเรา

 const ProductsTable = (props) => { const { products } = props; const [sortedField, setSortedField] = React.useState(null); let sortedProducts = [...products]; if (sortedField !== null) { sortedProducts.sort((a, b) => { if (a[sortedField] < b[sortedField]) { return -1; } if (a[sortedField] > b[sortedField]) { return 1; } return 0; }); } return ( <table>

ก่อนอื่นเราต้องตรวจสอบให้แน่ใจว่าเราได้เลือกฟิลด์ที่จะเรียงลำดับตาม และถ้าเป็นเช่นนั้น เราจะเรียงลำดับผลิตภัณฑ์ตามฟิลด์นั้น

จากน้อยไปมาก vs จากมากไปน้อย

คุณลักษณะต่อไปที่เราต้องการเห็นคือวิธีการสลับระหว่างลำดับจากน้อยไปมากและจากมากไปน้อย เราจะสลับระหว่างลำดับจากน้อยไปมากและจากมากไปน้อยโดยคลิกที่ส่วนหัวของตารางอีกครั้ง

ในการดำเนินการนี้ เราจำเป็นต้องแนะนำส่วนที่สอง — ลำดับการจัดเรียง เราจะปรับโครงสร้างตัวแปรสถานะ sortedField ปัจจุบันของเราเพื่อให้ทั้งชื่อฟิลด์และทิศทาง แทนที่จะมีสตริง ตัวแปรสถานะนี้จะมีอ็อบเจ็กต์ที่มีคีย์ (ชื่อฟิลด์) และทิศทาง เราจะเปลี่ยนชื่อเป็น sortConfig ให้ชัดเจนขึ้นเล็กน้อย

นี่คือฟังก์ชันการเรียงลำดับใหม่:

 sortedProducts.sort((a, b) => { if (a[sortConfig.key] < b[sortConfig.key]) { return sortConfig.direction === 'ascending' ? -1 : 1; } if (a[sortConfig.key] > b[sortConfig.key]) { return sortConfig.direction === 'ascending' ? 1 : -1; } return 0; });

ทีนี้ ถ้าทิศทางคือ 'จากน้อยไปมาก' เราจะทำเหมือนที่เคยทำมา ถ้าไม่ใช่ เราจะทำตรงกันข้าม ให้เราเรียงลำดับจากมากไปน้อย

ต่อไป เราจะสร้างฟังก์ชันใหม่ — requestSort — ซึ่งจะยอมรับชื่อฟิลด์และอัปเดตสถานะตามลำดับ

 const requestSort = key => { let direction = 'ascending'; if (sortConfig.key === key && sortConfig.direction === 'ascending') { direction = 'descending'; } setSortConfig({ key, direction }); }

นอกจากนี้เรายังต้องเปลี่ยนตัวจัดการการคลิกเพื่อใช้ฟังก์ชันใหม่นี้

 return ( <table> <thead> <tr> <th> <button type="button" onClick={() => requestSort('name')}> Name </button> </th> <th> <button type="button" onClick={() => requestSort('price')}> Price </button> </th> <th> <button type="button" onClick={() => requestSort('stock')}> In Stock </button> </th> </tr> </thead> {/* as before */} </table> );

ตอนนี้เราเริ่มที่จะดูมีฟีเจอร์ที่สมบูรณ์แล้ว แต่ยังมีเรื่องใหญ่ที่ต้องทำอีกเรื่องหนึ่ง เราจำเป็นต้องตรวจสอบให้แน่ใจว่าเราจัดเรียงข้อมูลของเราเมื่อจำเป็นเท่านั้น ขณะนี้ เรากำลังจัดเรียงข้อมูลทั้งหมดของเราในทุกการแสดงผล ซึ่งจะนำไปสู่ปัญหาด้านประสิทธิภาพทุกประเภท ให้ใช้เบ็ด useMemo ในตัวเพื่อบันทึกส่วนที่ช้าทั้งหมดแทน!

 const ProductsTable = (props) => { const { products } = props; const [sortConfig, setSortConfig] = React.useState(null); React.useMemo(() => { let sortedProducts = [...products]; if (sortedField !== null) { sortedProducts.sort((a, b) => { if (a[sortConfig.key] < b[sortConfig.key]) { return sortConfig.direction === 'ascending' ? -1 : 1; } if (a[sortConfig.key] > b[sortConfig.key]) { return sortConfig.direction === 'ascending' ? 1 : -1; } return 0; }); } return sortedProducts; }, [products, sortConfig]);

หากคุณไม่เคยเห็นมาก่อน useMemo เป็นวิธีแคชหรือบันทึก - การคำนวณที่มีราคาแพง ด้วยอินพุตเดียวกัน จึงไม่ต้องจัดเรียงผลิตภัณฑ์สองครั้ง หากเราแสดงส่วนประกอบของเราใหม่ด้วยเหตุผลบางประการ โปรดทราบว่าเราต้องการทริกเกอร์การจัดเรียงใหม่ทุกครั้งที่ผลิตภัณฑ์ของเราเปลี่ยนแปลง หรือฟิลด์หรือทิศทางที่เราจัดเรียงตามการเปลี่ยนแปลง

การห่อโค้ดของเราในฟังก์ชันนี้จะมีผลอย่างมากต่อการจัดเรียงตารางของเรา!

ทำให้ทุกอย่างนำกลับมาใช้ใหม่ได้

สิ่งที่ดีที่สุดอย่างหนึ่งเกี่ยวกับขอเกี่ยวคือความง่ายในการทำให้ตรรกะนำกลับมาใช้ใหม่ได้ คุณอาจจะจัดเรียงตารางทุกประเภทในแอปพลิเคชันของคุณ และต้องใช้งานสิ่งเดิมซ้ำๆ อีกครั้ง ดูเหมือนเป็นการลาก

React มีคุณสมบัตินี้เรียกว่า hooks แบบกำหนดเอง ฟังดูหรูหรา แต่ทั้งหมดนั้นเป็นฟังก์ชันปกติที่ใช้ตะขออื่นๆ ในตัว มาปรับโครงสร้างโค้ดของเราใหม่เพื่อให้อยู่ใน hook ที่กำหนดเองกัน เพื่อให้เราสามารถใช้งานได้ทุกที่!

 const useSortableData = (items, config = null) => { const [sortConfig, setSortConfig] = React.useState(config); const sortedItems = React.useMemo(() => { let sortableItems = [...items]; if (sortConfig !== null) { sortableItems.sort((a, b) => { if (a[sortConfig.key] < b[sortConfig.key]) { return sortConfig.direction === 'ascending' ? -1 : 1; } if (a[sortConfig.key] > b[sortConfig.key]) { return sortConfig.direction === 'ascending' ? 1 : -1; } return 0; }); } return sortableItems; }, [items, sortConfig]); const requestSort = key => { let direction = 'ascending'; if (sortConfig && sortConfig.key === key && sortConfig.direction === 'ascending') { direction = 'descending'; } setSortConfig({ key, direction }); } return { items: sortedItems, requestSort }; }

นี่เป็นการคัดลอกและวางจากโค้ดก่อนหน้าของเรา โดยมีการเปลี่ยนชื่อเล็กน้อย useSortableData ยอมรับรายการ และสถานะการจัดเรียงเริ่มต้นที่เป็นทางเลือก ส่งกลับวัตถุที่มีรายการที่เรียงลำดับ และฟังก์ชันเพื่อเรียงลำดับรายการใหม่

รหัสตารางของเราตอนนี้มีลักษณะดังนี้:

 const ProductsTable = (props) => { const { products } = props; const { items, requestSort } = useSortableData(products); return ( <table>{/* ... */}</table> ); };

สัมผัสสุดท้าย

มีชิ้นส่วนเล็กๆ หายไปหนึ่งชิ้น — วิธีระบุวิธีการจัดเรียงตาราง เพื่อระบุว่าในการออกแบบของเรา เราต้องส่งคืนสถานะภายในด้วย — sortConfig กลับมาใช้กันอีกครั้ง และใช้เพื่อสร้างสไตล์ที่เรานำไปใช้กับส่วนหัวของตารางได้!

 const ProductTable = (props) => { const { items, requestSort, sortConfig } = useSortableData(props.products); const getClassNamesFor = (name) => { if (!sortConfig) { return; } return sortConfig.key === name ? sortConfig.direction : undefined; }; return ( <table> <caption>Products</caption> <thead> <tr> <th> <button type="button" onClick={() => requestSort('name')} className={getClassNamesFor('name')} > Name </button> </th> {/* … */} </tr> </thead> {/* … */} </table> ); };

เท่านี้เราก็เสร็จแล้ว!

ห่อ

ปรากฏว่าการสร้างอัลกอริธึมการเรียงลำดับตารางของคุณเองไม่ใช่สิ่งที่เป็นไปไม่ได้เลย เราพบวิธีสร้างแบบจำลองสถานะของเรา เราเขียนฟังก์ชันการจัดเรียงทั่วไป และเราเขียนวิธีอัปเดตการตั้งค่าการจัดเรียงของเรา เราทำให้แน่ใจว่าทุกอย่างมีประสิทธิภาพและปรับโครงสร้างใหม่ทั้งหมดให้เป็นตะขอแบบกำหนดเอง สุดท้าย เราได้จัดเตรียมวิธีการระบุลำดับการจัดเรียงให้กับผู้ใช้

คุณสามารถดูการสาธิตของตารางใน CodeSandbox นี้: