วิธีเริ่มต้นใช้งาน VUE3.0 อย่างรวดเร็ว: เริ่มต้นใช้งาน
ในบทช่วยสอนนี้ เราจะได้เรียนรู้วิธีการนำ Memoization ไปใช้งานใน React การบันทึกช่วยปรับปรุงประสิทธิภาพโดยการแคชผลลัพธ์ของการเรียกใช้ฟังก์ชันและส่งคืนผลลัพธ์ที่แคชไว้เมื่อจำเป็นอีกครั้ง
เราจะกล่าวถึงสิ่งต่อไปนี้:
บทความนี้จะถือว่าคุณมีความเข้าใจพื้นฐานเกี่ยวกับส่วนประกอบของคลาสและฟังก์ชันใน React
หากคุณต้องการตรวจสอบหัวข้อเหล่านี้ คุณสามารถตรวจสอบ ส่วนประกอบเอกสารและอุปกรณ์ประกอบ ฉาก React อย่างเป็นทางการ
https://reactjs.org/docs/components-and-props.html
ก่อนที่จะพูดคุยถึงรายละเอียดของ Memoization ใน React ก่อนอื่นเรามาดูกันว่า React ใช้ DOM เสมือนเพื่อเรนเดอร์ UI อย่างไร
โดยทั่วไป DOM ปกติจะประกอบด้วยชุดของโหนดที่จัดอยู่ในรูปของต้นไม้ แต่ละโหนดใน DOM แสดงถึงองค์ประกอบ UI เมื่อใดก็ตามที่มีการเปลี่ยนแปลงสถานะเกิดขึ้นในแอปพลิเคชัน โหนดที่เกี่ยวข้องสำหรับองค์ประกอบ UI นั้นและองค์ประกอบย่อยทั้งหมดจะได้รับการอัปเดตในแผนผัง DOM ซึ่งจะทริกเกอร์การวาด UI ใหม่
ด้วยความช่วยเหลือของอัลกอริทึมแผนผัง DOM ที่มีประสิทธิภาพ การอัปเดตโหนดจะเร็วขึ้น แต่การวาดใหม่จะช้าและอาจส่งผลกระทบต่อประสิทธิภาพเมื่อ DOM มีองค์ประกอบ UI จำนวนมาก ดังนั้นจึงมีการนำ DOM เสมือนมาใช้ใน React
นี่คือการแสดงเสมือนจริงของ DOM จริง ตอนนี้เมื่อใดก็ตามที่มีการเปลี่ยนแปลงในสถานะของแอปพลิเคชัน React จะไม่อัปเดต DOM จริงโดยตรง แต่จะสร้าง DOM เสมือนใหม่ จากนั้น React จะเปรียบเทียบ DOM เสมือนใหม่นี้กับ DOM เสมือนที่สร้างขึ้นก่อนหน้านี้ ค้นหาความแตกต่าง (หมายเหตุของผู้แปล: นั่นคือค้นหาโหนดที่จำเป็นต้องอัปเดต) จากนั้นจึงวาดใหม่
จากความแตกต่างเหล่านี้ DOM เสมือนสามารถอัปเดต DOM จริงได้อย่างมีประสิทธิภาพมากขึ้น สิ่งนี้ช่วยปรับปรุงประสิทธิภาพเนื่องจาก DOM เสมือนไม่เพียงแค่อัปเดตองค์ประกอบ UI และองค์ประกอบลูกทั้งหมดเท่านั้น แต่ยังอัปเดตเฉพาะการเปลี่ยนแปลงที่จำเป็นและน้อยที่สุดใน DOM จริงเท่านั้น
ในส่วนก่อนหน้านี้ เราได้เห็นว่า React ใช้ DOM เสมือนเพื่อดำเนินการอัปเดต DOM เพื่อปรับปรุงประสิทธิภาพอย่างมีประสิทธิภาพได้อย่างไร ในส่วนนี้ เราจะแนะนำตัวอย่างที่อธิบายความจำเป็นในการใช้ Memoization เพื่อปรับปรุงประสิทธิภาพให้ดียิ่งขึ้น
เราจะสร้างคลาสพาเรนต์ที่มีปุ่มที่เพิ่มตัวแปรที่เรียกว่า count
องค์ประกอบหลักยังเรียกองค์ประกอบลูกและส่งพารามิเตอร์ไปให้ด้วย นอกจากนี้เรายังเพิ่มคำสั่ง console.log()
ในวิธี render
:
//Parent.js คลาสพาเรนต์ขยาย React.Component { ตัวสร้าง (อุปกรณ์ประกอบฉาก) { ซุปเปอร์(อุปกรณ์ประกอบฉาก); this.state = { นับ: 0 }; - handleClick = () => { this.setState((prevState) => { กลับ { นับ: prevState.count + 1 }; - - แสดงผล() { console.log("การเรนเดอร์พาเรนต์"); กลับ ( <div className="แอป"> <button onClick={this.handleClick}>การเพิ่มขึ้น</button> <h2>{this.state.count}</h2> <ชื่อเด็ก={"โจ"} /> </div> - - - ส่งออกค่าเริ่มต้น
โค้ดที่สมบูรณ์สำหรับตัวอย่างนี้สามารถดูได้บน CodeSandbox
เราจะสร้างคลาส Child
ที่ยอมรับพารามิเตอร์ที่ส่งผ่านโดยองค์ประกอบหลักและแสดงใน UI:
//Child.js คลาสเด็กขยาย React.Component { แสดงผล() { console.log("การเรนเดอร์เด็ก"); กลับ ( <div> <h2>{this.props.name}</h2> </div> - - - ส่งออกค่าเริ่มต้นลูก
ค่า count
จะเปลี่ยนทุกครั้งที่เราคลิกปุ่มในองค์ประกอบหลัก เนื่องจากสถานะมีการเปลี่ยนแปลง วิธี render
ขององค์ประกอบหลักจึงถูกดำเนินการ
พารามิเตอร์ที่ส่งไปยังคอมโพเนนต์ลูกจะไม่เปลี่ยนแปลงทุกครั้งที่คอมโพเนนต์หลักถูกเรนเดอร์ใหม่ ดังนั้นคอมโพเนนต์ลูกจึงไม่ควรเรนเดอร์ใหม่ อย่างไรก็ตาม เมื่อเรารันโค้ดด้านบนและยังคงเพิ่ม count
ต่อไป เราจะได้ผลลัพธ์ดังต่อไปนี้:
Parent render เด็กเรนเดอร์ เรนเดอร์ผู้ปกครอง เด็กเรนเดอร์ เรนเดอร์ผู้ปกครอง การแสดงผลลูก
คุณสามารถลองตัวอย่างข้างต้นในแซนด์บ็อกซ์นี้และดูเอาต์พุตคอนโซลได้
จากเอาต์พุต เราจะเห็นว่าเมื่อองค์ประกอบหลักแสดงผลซ้ำ องค์ประกอบย่อยจะแสดงผลซ้ำ แม้ว่าพารามิเตอร์ที่ส่งไปยังองค์ประกอบลูกยังคงไม่เปลี่ยนแปลงก็ตาม ซึ่งจะทำให้ DOM เสมือนของคอมโพเนนต์ลูกทำการตรวจสอบความแตกต่างกับ DOM เสมือนก่อนหน้า เนื่องจากไม่มีการเปลี่ยนแปลงในองค์ประกอบลูกของเรา และอุปกรณ์ประกอบฉากทั้งหมดไม่มีการเปลี่ยนแปลงในการเรนเดอร์ใหม่ DOM จริงจะไม่ได้รับการอัปเดต
เป็นประโยชน์ต่อประสิทธิภาพอย่างแน่นอนที่ DOM จริงไม่ได้รับการอัพเดตโดยไม่จำเป็น แต่เราจะเห็นว่า DOM เสมือนใหม่ถูกสร้างขึ้น และดำเนินการตรวจสอบความแตกต่าง แม้ว่าจะไม่มีการเปลี่ยนแปลงจริงในคอมโพเนนต์ลูกก็ตาม สำหรับส่วนประกอบ React ขนาดเล็ก ต้นทุนด้านประสิทธิภาพนี้มีน้อยมาก แต่สำหรับส่วนประกอบขนาดใหญ่ ผลกระทบต่อประสิทธิภาพอาจมีนัยสำคัญ เพื่อหลีกเลี่ยงการเรนเดอร์ซ้ำและการตรวจสอบความแตกต่างของ DOM เสมือน เราจึงใช้ Memoization
ในบริบทของแอปพลิเคชัน React การจดจำเป็นวิธีการที่เมื่อใดก็ตามที่องค์ประกอบหลักเรนเดอร์อีกครั้ง องค์ประกอบลูกจะเรนเดอร์ซ้ำเฉพาะเมื่ออุปกรณ์ประกอบฉากนั้นขึ้นอยู่กับการเปลี่ยนแปลง หากไม่มีการเปลี่ยนแปลงในอุปกรณ์ประกอบฉากที่องค์ประกอบลูกขึ้นอยู่กับ องค์ประกอบนั้นจะไม่ดำเนินการวิธีการเรนเดอร์และจะส่งคืนผลลัพธ์ที่แคชไว้ เนื่องจากไม่ได้ดำเนินการวิธีการเรนเดอร์ จะไม่มีการสร้าง DOM เสมือนและการตรวจสอบส่วนต่าง ส่งผลให้ประสิทธิภาพดีขึ้น
ตอนนี้เรามาดูวิธีการใช้ Memoization ในคลาสและส่วนประกอบของฟังก์ชันเพื่อหลีกเลี่ยงการเรนเดอร์ซ้ำโดยไม่จำเป็น
หากต้องการใช้ Memoization ในองค์ประกอบของคลาส เราจะใช้ React.PureComponent React.PureComponent
ใช้งาน shouldComponentUpdate() ซึ่งทำการเปรียบเทียบ state
และ props
แบบตื้น และเรนเดอร์ส่วนประกอบ React อีกครั้งหากอุปกรณ์ประกอบฉากหรือสถานะเปลี่ยนแปลง
เปลี่ยนองค์ประกอบย่อยให้เป็นโค้ดดังนี้:
//Child.js class Child ขยาย React.PureComponent { // ที่นี่เราเปลี่ยน React.Component เป็น React.PureComponent แสดงผล() { console.log("การเรนเดอร์เด็ก"); กลับ ( <div> <h2>{this.props.name}</h2> </div> - - - ส่งออกค่าเริ่มต้นลูก
รหัสที่สมบูรณ์สำหรับตัวอย่างนี้จะแสดงอยู่ในแซนด์บ็อกซ์นี้
องค์ประกอบหลักยังคงไม่เปลี่ยนแปลง ตอนนี้ เมื่อเราเพิ่ม count
ในองค์ประกอบหลัก ผลลัพธ์ในคอนโซลจะมีลักษณะดังนี้:
การแสดงผลหลัก เด็กเรนเดอร์ เรนเดอร์ผู้ปกครอง การแสดงผลพาเรนต์
สำหรับการเรนเดอร์ครั้งแรก จะเรียกวิธี render
ของทั้งคอมโพเนนต์พาเรนต์และคอมโพเนนต์ลูก
สำหรับการเรนเดอร์ซ้ำแต่ละครั้งหลังจากเพิ่ม count
จะเรียกเฉพาะฟังก์ชัน render
ของคอมโพเนนต์พาเรนต์เท่านั้น คอมโพเนนต์ย่อยจะไม่แสดงผลซ้ำ
หากต้องการใช้ Memoization ใน Function Components เราจะใช้ React.memo() React.memo()
เป็นส่วนประกอบที่มีลำดับสูงกว่า (HOC) ที่ทำงานคล้ายกับ PureComponent
เพื่อหลีกเลี่ยงการเรนเดอร์ซ้ำโดยไม่จำเป็น
นี่คือโค้ดสำหรับส่วนประกอบของฟังก์ชัน:
//Child.js ฟังก์ชั่นการส่งออก Child (อุปกรณ์ประกอบฉาก) { console.log("การเรนเดอร์เด็ก"); กลับ ( <div> <h2>{อุปกรณ์ประกอบฉาก.ชื่อ}</h2> </div> - - ส่งออกค่าเริ่มต้น React.memo(Child); // ที่นี่เราเพิ่ม HOC ให้กับองค์ประกอบลูกเพื่อใช้งาน Memoization
และยังแปลงองค์ประกอบหลักให้เป็นองค์ประกอบฟังก์ชั่นดังที่แสดงด้านล่าง:
//Parent.js ส่งออกฟังก์ชันเริ่มต้น Parent() { const [นับ setCount] = useState (0); const handleClick = () => { setCount(นับ + 1); - console.log("การเรนเดอร์พาเรนต์"); กลับ ( <div> <button onClick={handleClick}>การเพิ่มขึ้น</button> <h2>{นับ}</h2> <ชื่อเด็ก={"โจ"} /> </div> - }
สามารถดูโค้ดที่สมบูรณ์สำหรับตัวอย่างนี้ได้ในแซนด์บ็อกซ์นี้
ตอนนี้ เมื่อเราเพิ่ม count
ในองค์ประกอบหลัก ผลลัพธ์ต่อไปนี้จะถูกส่งออกไปยังคอนโซล:
การเรนเดอร์พาเรนต์ เด็กเรนเดอร์ เรนเดอร์ผู้ปกครอง เรนเดอร์ผู้ปกครอง
การเรนเดอร์พาเรนต์React.memo()
ในตัวอย่างข้างต้น เราจะเห็นว่าเมื่อเราใช้ React.memo()
HOC บนคอมโพเนนต์ลูก คอมโพเนนต์ลูกจะไม่ถูกเรนเดอร์ซ้ำ แม้ว่าคอมโพเนนต์พาเรนต์จะถูกเรนเดอร์ใหม่ก็ตาม
อย่างไรก็ตาม สิ่งเล็กๆ น้อยๆ ที่ควรทราบก็คือ ถ้าเราส่งฟังก์ชันเป็นพารามิเตอร์ไปยังคอมโพเนนต์ลูก คอมโพเนนต์ลูกจะเรนเดอร์อีกครั้งแม้ว่าจะใช้ React.memo()
แล้วก็ตาม ลองดูตัวอย่างนี้
เราจะเปลี่ยนองค์ประกอบหลักดังที่แสดงด้านล่าง ที่นี่เราเพิ่มฟังก์ชันตัวจัดการและส่งผ่านเป็นพารามิเตอร์ไปยังองค์ประกอบย่อย:
//Parent.js ส่งออกฟังก์ชันเริ่มต้น Parent() { const [นับ setCount] = useState (0); const handleClick = () => { setCount(นับ + 1); - ตัวจัดการ const = () => { console.log("handler"); // ฟังก์ชันตัวจัดการจะถูกส่งไปยังองค์ประกอบลูก}; console.log("การเรนเดอร์พาเรนต์"); กลับ ( <div className="แอป"> <button onClick={handleClick}>การเพิ่มขึ้น</button> <h2>{นับ}</h2> <ชื่อลูก={"joe"} childFunc={ตัวจัดการ} /> </div> - }
โค้ดส่วนประกอบย่อยจะยังคงเหมือนเดิม เราจะไม่ใช้ฟังก์ชันที่ส่งผ่านจากส่วนประกอบหลักในส่วนประกอบย่อย:
//Child.js ฟังก์ชั่นการส่งออก Child (อุปกรณ์ประกอบฉาก) { console.log("การเรนเดอร์เด็ก"); กลับ ( <div> <h2>{อุปกรณ์ประกอบฉาก.ชื่อ}</h2> </div> - - ส่งออกค่าเริ่มต้น React.memo(Child);
ตอนนี้เมื่อเราเพิ่ม count
ในองค์ประกอบหลัก มันจะเรนเดอร์ใหม่และเรนเดอร์องค์ประกอบลูกอีกครั้งในเวลาเดียวกัน แม้ว่าจะไม่มีการเปลี่ยนแปลงในพารามิเตอร์ที่ส่งผ่านก็ตาม
แล้วอะไรทำให้ส่วนประกอบย่อยแสดงผลอีกครั้ง? คำตอบก็คือ ทุกครั้งที่คอมโพเนนต์พาเรนต์เรนเดอร์ใหม่ ฟังก์ชัน handler
ใหม่จะถูกสร้างขึ้นและส่งผ่านไปยังคอมโพเนนต์ลูก ในตอนนี้ เนื่องจากฟังก์ชัน handle
ถูกสร้างขึ้นใหม่ในการเรนเดอร์ทุกครั้ง คอมโพเนนต์ลูกจะพบว่าการอ้างอิง handler
เปลี่ยนไปเมื่อทำการเปรียบเทียบอุปกรณ์ประกอบฉากแบบตื้น และเรนเดอร์คอมโพเนนต์ลูกอีกครั้ง
ต่อไป เราจะกล่าวถึงวิธีแก้ปัญหานี้
useCallback()
เพื่อหลีกเลี่ยงการเรนเดอร์ซ้ำมากขึ้นปัญหาหลักของการเรนเดอร์คอมโพเนนต์ย่อยคือฟังก์ชัน handler
ถูกสร้างขึ้นใหม่ ซึ่งจะเปลี่ยนการอ้างอิงที่ส่งผ่านไปยังคอมโพเนนต์ลูก ดังนั้นเราจึงต้องมีวิธีหลีกเลี่ยงความซ้ำซ้อนนี้ หากไม่ได้สร้างฟังก์ชัน handler
ขึ้นใหม่ การอ้างอิงถึงฟังก์ชัน handler
จะไม่เปลี่ยนแปลง ดังนั้นคอมโพเนนต์ลูกจึงไม่แสดงผลซ้ำ
เพื่อหลีกเลี่ยงการต้องสร้างฟังก์ชันใหม่ทุกครั้งที่เรนเดอร์องค์ประกอบหลัก เราจะใช้ React Hook ที่เรียกว่า useCallback() Hooks ถูกนำมาใช้ใน React 16 หากต้องการเรียนรู้เพิ่มเติมเกี่ยวกับ Hooks คุณสามารถดูเอกสาร hooks อย่างเป็นทางการของ React หรือดู `React Hooks: วิธีเริ่มต้นใช้งานและสร้างของคุณเอง"
ฮุก useCallback()
ใช้พารามิเตอร์สองตัว: ฟังก์ชันเรียกกลับและรายการการขึ้นต่อกัน
ไป
นี้เป็นตัวอย่างของ useCallback()
:
const handleClick = useCallback(() => { //ทำอะไรสักอย่าง }, [x,y]);
ในที่นี้ useCallback()
จะถูกเพิ่มเข้าไปในฟังก์ชัน handleClick()
พารามิเตอร์ที่สอง [x, y]
อาจเป็นอาร์เรย์ว่าง การขึ้นต่อกันเดียว หรือรายการการขึ้นต่อกัน ฟังก์ชัน handleClick()
จะถูกสร้างขึ้นใหม่เฉพาะเมื่อมีการอ้างอิงใดๆ ที่กล่าวถึงในการเปลี่ยนแปลงพารามิเตอร์ครั้งที่สองเท่านั้น
หากการขึ้นต่อกันที่กล่าวถึงใน useCallback()
ไม่มีการเปลี่ยนแปลง ฟังก์ชันการโทรกลับเวอร์ชัน Memoized ที่กล่าวถึงเป็นอาร์กิวเมนต์แรกจะถูกส่งกลับ เราจะเปลี่ยนองค์ประกอบหลักเพื่อใช้ตะขอ useCallback()
บนตัวจัดการที่ส่งผ่านไปยังองค์ประกอบลูก:
//Parent.js ส่งออกฟังก์ชันเริ่มต้น Parent() { const [นับ setCount] = useState (0); const handleClick = () => { setCount(นับ + 1); - const handler = useCallback(() => { // ใช้ useCallback() สำหรับฟังก์ชันตัวจัดการ console.log("ตัวจัดการ"); - console.log("การเรนเดอร์พาเรนต์"); กลับ ( <div className="แอป"> <button onClick={handleClick}>การเพิ่มขึ้น</button> <h2>{นับ}</h2> <ชื่อลูก={"joe"} childFunc={ตัวจัดการ} /> </div> - }
โค้ดส่วนประกอบย่อยจะยังคงเหมือนเดิม
รหัสที่สมบูรณ์สำหรับตัวอย่างนี้อยู่ในแซนด์บ็อกซ์นี้
เมื่อเราเพิ่ม count
ในองค์ประกอบหลักของโค้ดด้านบน เราจะเห็นผลลัพธ์ต่อไปนี้:
การเรนเดอร์พาเรนต์ เด็กเรนเดอร์ เรนเดอร์ผู้ปกครอง เรนเดอร์ผู้ปกครอง เรนเดอร์พาเรนต์
เนื่องจากเราใช้ตะขอ useCallback()
สำหรับ handler
ในคอมโพเนนต์พาเรนต์ ฟังก์ชัน handler
จะไม่ถูกสร้างขึ้นใหม่ทุกครั้งที่คอมโพเนนต์พาเรนต์ถูกเรนเดอร์อีกครั้ง และเวอร์ชัน Memoization ของ handler
จะถูกส่งไปยังคอมโพเนนต์ลูก คอมโพเนนต์ลูกจะทำการเปรียบเทียบแบบตื้นและสังเกตว่าการอ้างอิงไปยังฟังก์ชัน handler
ไม่มีการเปลี่ยนแปลง ดังนั้นจะไม่เรียกเมธอดการ render
Memoization เป็นวิธีที่ดีในการหลีกเลี่ยงการเรนเดอร์ส่วนประกอบซ้ำโดยไม่จำเป็น เมื่อสถานะหรืออุปกรณ์ประกอบฉากไม่มีการเปลี่ยนแปลง จึงช่วยปรับปรุงประสิทธิภาพของแอปพลิเคชัน React คุณอาจพิจารณาเพิ่ม Memoization ให้กับส่วนประกอบทั้งหมดของคุณ แต่นั่นไม่ใช่วิธีสร้างส่วนประกอบ React ที่มีประสิทธิภาพสูงเสมอไป การจดจำควรใช้เฉพาะในกรณีที่ส่วนประกอบ:
ในบทช่วยสอนนี้เราเข้าใจ:
React.memo()
สำหรับส่วนประกอบของฟังก์ชัน และ React.PureComponent
สำหรับส่วนประกอบของคลาสReact.memo()
แล้วก็ตามuseCallback()
หลีกเลี่ยงปัญหาการเรนเดอร์ซ้ำเมื่อมีการส่งฟังก์ชันเป็นอุปกรณ์ประกอบฉากไปยังคอมโพเนนต์ย่อยฉันหวังว่าการแนะนำ React Memoization นี้จะเป็นประโยชน์กับคุณ!
ที่อยู่เดิม: https://www.sitepoint.com/implement-memoization-in-react-to-improve-Performance/
ผู้เขียนต้นฉบับ: Nida Khan