ทำความเข้าใจพฤติกรรมการแสดงผลใน React
เผยแพร่แล้ว: 2020-11-16นอกเหนือจากชีวิต ความตาย โชคชะตา และภาษีแล้ว พฤติกรรมการแสดงของ React เป็นหนึ่งในความจริงและความลึกลับที่ยิ่งใหญ่ที่สุดในชีวิต
มาดำน้ำกันเถอะ!
เช่นเดียวกับคนอื่นๆ ฉันเริ่มเส้นทางการพัฒนาส่วนหน้าด้วย jQuery การจัดการ DOM บน JS ล้วนเป็นฝันร้ายในตอนนั้น ดังนั้นมันจึงเป็นสิ่งที่ทุกคนกำลังทำอยู่ จากนั้นอย่างช้าๆ เฟรมเวิร์กที่ใช้ JavaScript ก็มีความโดดเด่นมากจนผมไม่สามารถละเลยมันได้อีกต่อไป
สิ่งแรกที่ฉันเรียนรู้คือ Vue ฉันมีช่วงเวลาที่ยากลำบากอย่างเหลือเชื่อเพราะองค์ประกอบและสภาพและทุกอย่างเป็นแบบจำลองทางความคิดใหม่ทั้งหมด และมันก็เป็นความเจ็บปวดอย่างมากที่จะใส่ทุกอย่างเข้าที่เข้าทาง แต่ในที่สุดฉันก็ทำได้ และตบหลังตัวเอง ยินดีด้วย เพื่อน ฉันบอกตัวเองว่า คุณปีนได้สูงชันแล้ว ตอนนี้ เฟรมเวิร์กที่เหลือ หากคุณต้องการเรียนรู้มัน จะเป็นเรื่องง่ายมาก
ดังนั้น วันหนึ่ง เมื่อฉันเริ่มเรียนรู้ React ฉันก็รู้ว่าตัวเองคิดผิดอย่างมหันต์ Facebook ไม่ได้ทำให้สิ่งต่างๆ ง่ายขึ้นด้วยการใช้ Hooks และบอกกับทุกคนว่า “เดี๋ยวก่อน ใช้สิ่งนี้ต่อจากนี้ไป แต่อย่าเขียนคลาสใหม่ ชั้นเรียนก็ดี อันที่จริงก็ไม่เท่าไหร่ แต่ก็โอเค แต่ฮุคคือทุกสิ่ง และพวกเขาคืออนาคต
เข้าใจแล้ว? ยอดเยี่ยม!".
ในที่สุดฉันก็ข้ามภูเขานั้นด้วย แต่แล้วฉันก็โดนบางอย่างที่สำคัญและยากพอๆ กับ React เอง: rendering

หากคุณเคยเจอการเรนเดอร์และความลึกลับของมันใน React คุณคงรู้ว่าฉันกำลังพูดถึงอะไร และถ้าคุณยังไม่มี คุณก็ไม่รู้ว่าจะมีอะไรรอคุณอยู่!
แต่ก่อนที่จะเสียเวลาไปกับอะไรซักอย่าง เป็นนิสัยที่ดีที่จะถามว่าคุณจะได้อะไรจากสิ่งนั้น หากชีวิตของคุณในฐานะนักพัฒนา React ดำเนินไปด้วยดีโดยไม่ต้องกังวลว่าการเรนเดอร์นี้คืออะไร ทำไมต้องสนใจ เป็นคำถามที่ดี เรามาตอบคำถามนี้ก่อน แล้วเราจะมาดูกันว่าการเรนเดอร์จริงๆ คืออะไร
ทำไมการเข้าใจพฤติกรรมการเรนเดอร์ใน React จึงสำคัญ?
เราทุกคนเริ่มเรียนรู้ React โดยการเขียนส่วนประกอบ (ทุกวันนี้ ใช้งานได้จริง) ที่ส่งคืนสิ่งที่เรียกว่า JSX เรายังเข้าใจด้วยว่า JSX นี้ถูกแปลงเป็นองค์ประกอบ HTML DOM จริงที่แสดงบนหน้า เพจจะอัปเดตเมื่อสถานะอัปเดต เส้นทางเปลี่ยนแปลงตามที่คาดไว้ และทุกอย่างเรียบร้อยดี แต่มุมมองที่ว่า React ทำงานอย่างไรนั้นไร้เดียงสาและเป็นสาเหตุของปัญหามากมาย
แม้ว่าเรามักจะประสบความสำเร็จในการเขียนแอปที่ใช้ React ให้สมบูรณ์ แต่ก็มีบางครั้งที่เราพบว่าบางส่วนของแอปพลิเคชันของเรา (หรือแอปพลิเคชันทั้งหมด) ช้าอย่างน่าทึ่ง และส่วนที่แย่ที่สุด . . เราไม่มีเงื่อนงำเลยว่าทำไม! เราได้ทำทุกอย่างถูกต้องแล้ว ไม่พบข้อผิดพลาดหรือคำเตือนใดๆ เราปฏิบัติตามแนวทางปฏิบัติที่ดีทั้งหมดในการออกแบบส่วนประกอบ มาตรฐานการเข้ารหัส ฯลฯ และไม่มีการทำงานช้าของเครือข่ายหรือการคำนวณเชิงตรรกะทางธุรกิจที่มีราคาแพงอยู่เบื้องหลัง
บางครั้งก็เป็นปัญหาที่แตกต่างไปจากเดิมอย่างสิ้นเชิง: ไม่มีอะไรผิดปกติกับประสิทธิภาพ แต่แอปทำงานผิดปกติ ตัวอย่างเช่น การเรียก API สามครั้งไปยังแบ็กเอนด์การตรวจสอบสิทธิ์ แต่จะเรียกใช้เพียงครั้งเดียวกับผู้อื่นทั้งหมดเท่านั้น หรือบางหน้ามีการวาดใหม่สองครั้ง โดยจะเห็นการเปลี่ยนแปลงระหว่างการเรนเดอร์สองหน้าของหน้าเดียวกันทำให้เกิด UX ที่สั่นสะเทือน

ที่แย่ที่สุดคือไม่มีความช่วยเหลือจากภายนอกในกรณีเช่นนี้ หากคุณไปที่ฟอรัม dev ที่คุณชื่นชอบและถามคำถามนี้ พวกเขาจะตอบว่า "บอกไม่ได้ถ้าไม่ได้ดูแอปของคุณ คุณสามารถแนบตัวอย่างการทำงานขั้นต่ำที่นี่ได้ไหม” แน่นอนว่าคุณไม่สามารถแนบแอปทั้งหมดได้ด้วยเหตุผลทางกฎหมาย ในขณะที่ตัวอย่างการทำงานเล็กๆ ของส่วนนั้นอาจไม่มีปัญหาดังกล่าว เนื่องจากแอปไม่ได้โต้ตอบกับทั้งระบบเหมือนในแอปจริง
เมา? ใช่ถ้าคุณถามฉัน
ดังนั้น เว้นแต่คุณอยากเห็นวันแห่งความวิบัติเช่นนี้ ฉันแนะนำให้คุณพัฒนาความเข้าใจ และต้องสนใจ ฉันต้องยืนกราน ความเข้าใจที่ได้รับอย่างไม่เต็มใจจะไม่นำคุณไปไกลในโลก React — ในสิ่งที่เข้าใจยากซึ่งเรียกว่าการเรนเดอร์ใน React เชื่อฉันเถอะ มันไม่ได้ยากขนาดนั้นที่จะเข้าใจ และถึงแม้ว่ามันจะยากมากที่จะเชี่ยวชาญ คุณจะไปได้ไกลโดยไม่ต้องรู้ทุกซอกทุกมุม
การเรนเดอร์หมายถึงอะไรใน React?
นั่นเพื่อนของฉันเป็นคำถามที่ยอดเยี่ยม เรามักไม่ค่อยถามเมื่อเรียน React (ฉันรู้เพราะฉันไม่รู้) เพราะคำว่า "render" อาจกล่อมเราให้รู้สึกคุ้นเคย แม้ว่าความหมายของพจนานุกรมจะแตกต่างไปจากเดิมอย่างสิ้นเชิง (และไม่สำคัญในการสนทนานี้) โปรแกรมเมอร์เราก็มีแนวคิดอยู่แล้วว่าควรหมายความว่าอย่างไร การทำงานกับหน้าจอ, 3D APIs, กราฟิกการ์ด และข้อกำหนดของผลิตภัณฑ์ในการอ่านจะฝึกจิตใจของเราให้นึกถึงบางสิ่งในแนว "วาดภาพ" เมื่อเราอ่านคำว่า "เรนเดอร์" ในการเขียนโปรแกรมเอ็นจิ้นเกม มี Renderer ซึ่งมีหน้าที่เพียงอย่างเดียวคือ ระบายสีโลกตามที่ Scene มอบให้
ดังนั้นเราจึงคิดว่าเมื่อ React "แสดงผล" บางอย่าง มันจะรวบรวมส่วนประกอบทั้งหมดและทาสี DOM ของหน้าเว็บใหม่ แต่ในโลกของ React (และใช่ แม้แต่ในเอกสารอย่างเป็นทางการ) นั่นไม่ใช่สิ่งที่เกี่ยวกับการเรนเดอร์ ดังนั้น มารัดเข็มขัดนิรภัยให้แน่นและดำดิ่งลึกลงไปใน React internals

คุณต้องเคยได้ยินว่า React รักษาสิ่งที่เรียกว่า DOM เสมือน และมันเปรียบเทียบกับ DOM จริงเป็นระยะ และใช้การเปลี่ยนแปลงตามความจำเป็น (นี่คือเหตุผลที่คุณไม่สามารถรวม jQuery และ React เข้าด้วยกัน — React จำเป็นต้องควบคุมทั้งหมด ดอม) ตอนนี้ DOM เสมือนนี้ไม่ได้ประกอบด้วยองค์ประกอบ HTML เหมือนที่ DOM จริงมี แต่ประกอบด้วยองค์ประกอบ React อะไรคือความแตกต่าง? คำถามที่ดี! ทำไมไม่ลองสร้างแอพ React เล็กๆ ขึ้นมาดูเองล่ะ?
ฉันสร้างแอป React ที่ง่ายมากสำหรับจุดประสงค์นี้ รหัสทั้งหมดเป็นเพียงไฟล์เดียวที่มีสองสามบรรทัด:
import React from "react"; import "./styles.css"; export default function App() { const element = ( <div className="App"> <h1>Hello, there!</h1> <h2>Let's take a look inside React elements</h2> </div> ); console.log(element); return element; }
สังเกตว่าเรากำลังทำอะไรที่นี่?
ใช่ เพียงแค่บันทึกว่าองค์ประกอบ JSX เป็นอย่างไร นิพจน์และส่วนประกอบ JSX เหล่านี้เป็นสิ่งที่เราเขียนมาหลายร้อยครั้ง แต่เราไม่ค่อยใส่ใจกับสิ่งที่เกิดขึ้น หากคุณเปิดคอนโซลนักพัฒนาซอฟต์แวร์ของเบราว์เซอร์และเรียกใช้แอปนี้ คุณจะเห็น Object
ที่ขยายเป็น:

สิ่งนี้อาจดูน่ากลัว แต่ให้สังเกตรายละเอียดที่น่าสนใจบางประการ:
- ที่เรากำลังดูอยู่นั้นเป็นออบเจกต์ JavaScript แบบธรรมดาและไม่ใช่โหนด DOM
- โปรดสังเกตว่าพร็อพพ
props
อพเพอร์ตี้บอกว่ามีclassName
ของApp
(ซึ่งเป็นคลาส CSS ที่ตั้งค่าไว้ในโค้ด) และองค์ประกอบนี้มีลูกสองคน (ซึ่งตรงกันด้วย องค์ประกอบย่อยคือแท็ก<h1>
และ<h2>
) . - คุณสมบัติ
_source
บอกเราว่าซอร์สโค้ดเริ่มต้นเนื้อหาขององค์ประกอบที่ใด อย่างที่คุณเห็น มันตั้งชื่อไฟล์App.js
เป็นแหล่งที่มาและระบุบรรทัดที่ 6 หากคุณดูโค้ดอีกครั้ง คุณจะพบว่าบรรทัดที่ 6 อยู่หลังแท็กเปิด JSX ซึ่งสมเหตุสมผล วงเล็บ JSX มี องค์ประกอบ React; พวกมันไม่ได้เป็นส่วนหนึ่งของมัน เนื่องจากพวกมันทำหน้าที่แปลงเป็นการเรียกReact.createElement()
ในภายหลัง - คุณสมบัติ
__proto__
บอกเราว่าอ็อบเจกต์นี้มาจากทั้งหมด คุณสมบัติจากรูท JavaScriptObject
ตอกย้ำแนวคิดอีกครั้งว่าเป็นเพียงออบเจกต์ JavaScript ประจำวันที่เรากำลังดูอยู่
ดังนั้น ตอนนี้ เราเข้าใจว่าสิ่งที่เรียกว่า virtual DOM นั้นไม่ได้ดูเหมือน DOM จริง แต่เป็นแผนผังของวัตถุ React (JavaScript) ที่เป็นตัวแทนของ UI ณ เวลานั้น

เหนื่อย?
เชื่อฉัน ฉันก็เหมือนกัน การพลิกความคิดเหล่านี้ซ้ำแล้วซ้ำเล่าในหัวของฉันเพื่อพยายามนำเสนอในวิธีที่ดีที่สุดเท่าที่จะเป็นไปได้ จากนั้นจึงคิดคำที่จะนำออกมาและจัดเรียงใหม่ ไม่ใช่เรื่องง่าย
แต่เรากำลังฟุ้งซ่าน!
หลังจากที่รอดมาได้จนถึงตอนนี้ เราก็อยู่ในฐานะที่จะตอบคำถามที่เรากำลังตามหา: อะไรคือการเรนเดอร์ใน React?
การเรนเดอร์เป็นกระบวนการของกลไก React ที่เดินผ่าน DOM เสมือนและรวบรวมสถานะปัจจุบัน อุปกรณ์ประกอบฉาก โครงสร้าง การเปลี่ยนแปลงที่ต้องการใน UI ฯลฯ ตอนนี้ React จะอัปเดต DOM เสมือนโดยใช้การคำนวณและเปรียบเทียบผลลัพธ์ใหม่กับ DOM จริง บนหน้า การคำนวณและการเปรียบเทียบนี้คือสิ่งที่ทีม React เรียกอย่างเป็นทางการว่า "การกระทบยอด" และหากคุณสนใจแนวคิดและอัลกอริทึมที่เกี่ยวข้อง คุณสามารถตรวจสอบเอกสารอย่างเป็นทางการได้
ถึงเวลามุ่งมั่น!
เมื่อส่วนการเรนเดอร์เสร็จสิ้น React จะเริ่มเฟสที่เรียกว่า "commit" ซึ่งในระหว่างนั้นจะใช้การเปลี่ยนแปลงที่จำเป็นกับ DOM การเปลี่ยนแปลงเหล่านี้ถูกนำไปใช้พร้อมกัน (ทีละอย่าง แม้ว่าโหมดใหม่จะทำงานพร้อมกันในเร็วๆ นี้) และ DOM จะได้รับการอัปเดต เมื่อใดและอย่างไรที่ React ใช้การเปลี่ยนแปลงเหล่านี้ไม่ใช่สิ่งที่เรากังวล เนื่องจากเป็นสิ่งที่อยู่ภายใต้การควบคุมทั้งหมดและมีแนวโน้มที่จะเปลี่ยนแปลงต่อไปเมื่อทีม React ทดลองสิ่งใหม่ๆ
การแสดงผลและประสิทธิภาพในแอป React
ตอนนี้เราเข้าใจแล้วว่าการเรนเดอร์หมายถึงการรวบรวมข้อมูล และไม่จำเป็นต้องส่งผลให้เกิดการเปลี่ยนแปลง DOM แบบเห็นภาพทุกครั้ง เราทราบด้วยว่าสิ่งที่เราพิจารณาว่าเป็น "การแสดงผล" เป็นกระบวนการสองขั้นตอนที่เกี่ยวข้องกับการเรนเดอร์และคอมมิต ตอนนี้เราจะมาดูกันว่าการเรนเดอร์ (และที่สำคัญกว่านั้นคือ การเรนเดอร์ซ้ำ) ถูกทริกเกอร์ในแอป React อย่างไร และการไม่รู้รายละเอียดอาจทำให้แอปทำงานได้ไม่ดี
แสดงผลซ้ำเนื่องจากมีการเปลี่ยนแปลงในองค์ประกอบหลัก
หากองค์ประกอบหลักใน React เปลี่ยนแปลง (เช่น เนื่องจากสถานะหรืออุปกรณ์ประกอบฉากเปลี่ยนแปลง) React จะเดินทั้งทรีลงมาตามองค์ประกอบหลักนี้และแสดงส่วนประกอบทั้งหมดอีกครั้ง หากแอปพลิเคชันของคุณมีองค์ประกอบที่ซ้อนกันจำนวนมากและการโต้ตอบจำนวนมาก คุณกำลังได้รับผลกระทบอย่างมากทุกครั้งที่คุณเปลี่ยนองค์ประกอบหลัก (สมมติว่าเป็นเพียงองค์ประกอบหลักที่คุณต้องการเปลี่ยน)

จริง การเรนเดอร์จะไม่ทำให้ React เปลี่ยน DOM จริง เพราะในระหว่างการกระทบยอด จะตรวจพบว่าไม่มีการเปลี่ยนแปลงสำหรับส่วนประกอบเหล่านี้ แต่มันยังใช้เวลา CPU และหน่วยความจำที่สูญเปล่า และคุณจะแปลกใจที่มันเพิ่มขึ้นอย่างรวดเร็ว
แสดงผลซ้ำเนื่องจากการเปลี่ยนแปลงในบริบท
ฟีเจอร์บริบทของ React ดูเหมือนจะเป็นเครื่องมือจัดการสถานะที่ทุกคนชื่นชอบ (ซึ่งไม่ได้สร้างมาเพื่อสิ่งนี้เลย) ทั้งหมดนี้สะดวกมาก — เพียงแค่ห่อองค์ประกอบบนสุดในผู้ให้บริการบริบท ที่เหลือก็เป็นเรื่องง่าย! แอพ React ส่วนใหญ่ถูกสร้างขึ้นในลักษณะนี้ แต่ถ้าคุณอ่านบทความนี้มาจนถึงตอนนี้ คุณอาจพบว่ามีอะไรผิดปกติ ใช่ ทุกครั้งที่มีการอัปเดตออบเจ็กต์บริบท ออบเจ็กต์บริบทจะทริกเกอร์การแสดงผลซ้ำจำนวนมากของคอมโพเนนต์ทรีทั้งหมด
แอปส่วนใหญ่ไม่มีการรับรู้ถึงประสิทธิภาพ ดังนั้นจึงไม่มีใครสังเกตเห็น แต่อย่างที่กล่าวไว้ก่อนหน้านี้ การกำกับดูแลดังกล่าวอาจมีราคาแพงมากในแอปที่มีปริมาณมากและมีปฏิสัมพันธ์สูง
ปรับปรุงประสิทธิภาพการเรนเดอร์ React
จากทั้งหมดนี้ เราจะทำอะไรได้บ้างเพื่อปรับปรุงประสิทธิภาพของแอปของเรา ปรากฏว่ามีบางสิ่งที่เราสามารถทำได้ แต่โปรดทราบว่าเราจะพูดถึงเฉพาะในบริบทขององค์ประกอบการทำงานเท่านั้น ส่วนประกอบตามคลาสนั้นถูกกีดกันอย่างมากจากทีม React และกำลังจะออกไป
ใช้ Redux หรือไลบรารีที่คล้ายกันสำหรับการจัดการสถานะ
ผู้ที่ชื่นชอบโลกแห่งบริบทที่รวดเร็วและสกปรกมักจะเกลียด Redux แต่สิ่งนี้ได้รับความนิยมอย่างมหาศาลด้วยเหตุผลที่ดี และหนึ่งในเหตุผลเหล่านี้ก็คือประสิทธิภาพ — ฟังก์ชัน connect()
ใน Redux นั้นวิเศษมาก เนื่องจาก (เกือบทุกครั้ง) แสดงเฉพาะส่วนประกอบเหล่านั้นอย่างถูกต้องตามความจำเป็น ใช่ เพียงทำตามสถาปัตยกรรม Redux มาตรฐานและประสิทธิภาพก็ฟรี ไม่ใช่เรื่องเกินจริงแต่อย่างใดหากคุณนำสถาปัตยกรรม Redux มาใช้ คุณจะหลีกเลี่ยงปัญหาด้านประสิทธิภาพส่วนใหญ่ (และอื่นๆ) ในทันที
ใช้ memo()
เพื่อ "หยุด" ส่วนประกอบ
ชื่อ "บันทึก" มาจาก Memoization ซึ่งเป็นชื่อแฟนซีสำหรับการแคช และถ้าคุณไม่เคยเจอแคชมากนัก ก็ไม่เป็นไร นี่คือคำอธิบายที่ไม่ค่อยดีนัก: ทุกครั้งที่คุณต้องการผลการคำนวณ/การดำเนินการ คุณมองหาตำแหน่งที่คุณรักษาผลลัพธ์ก่อนหน้านี้ไว้ หากคุณพบว่ามันยอดเยี่ยม เพียงแค่ส่งคืนผลลัพธ์นั้น ถ้าไม่ ให้ดำเนินการต่อไป/คำนวณ
ก่อนดำดิ่งสู่ memo()
มาดูว่าการเรนเดอร์ที่ไม่จำเป็นเกิดขึ้นใน React อย่างไร เราเริ่มต้นด้วยสถานการณ์ที่ตรงไปตรงมา: ส่วนเล็กๆ ของ UI ของแอปที่แสดงให้ผู้ใช้เห็นว่าพวกเขาชอบบริการ/ผลิตภัณฑ์กี่ครั้ง (หากคุณมีปัญหาในการยอมรับกรณีใช้งาน ลองนึกถึงวิธีการ "ปรบมือ" ในสื่อ ” หลายครั้งเพื่อแสดงว่าคุณสนับสนุน/ชอบบทความมากแค่ไหน)
นอกจากนี้ยังมีปุ่มที่ช่วยให้พวกเขาเพิ่มจำนวนการชอบได้ 1 และสุดท้าย มีองค์ประกอบอื่นภายในที่แสดงรายละเอียดบัญชีพื้นฐานแก่ผู้ใช้ อย่ากังวลไปเลยหากคุณพบว่าสิ่งนี้ยากต่อการปฏิบัติตาม ตอนนี้ฉันจะให้รหัสทีละขั้นตอนสำหรับทุกอย่าง (และก็ไม่มีอะไรมาก) และในตอนท้าย ลิงก์ไปยังสนามเด็กเล่นที่คุณสามารถยุ่งกับแอปที่ใช้งานได้และปรับปรุงความเข้าใจของคุณ
มาจัดการองค์ประกอบเกี่ยวกับข้อมูลลูกค้ากันก่อน มาสร้างไฟล์ชื่อ CustomerInfo.js
ที่มีรหัสต่อไปนี้:
import React from "react"; export const CustomerInfo = () => { console.log("CustomerInfo was rendered! :O"); return ( <React.Fragment> <p>Name: Sam Punia</p> <p>Email: [email protected]</p> <p>Preferred method: Online</p> </React.Fragment> ); };
ไม่มีอะไรแฟนซีใช่มั้ย?
เป็นเพียงข้อความให้ข้อมูลบางส่วน (ซึ่งสามารถส่งต่อผ่านอุปกรณ์ประกอบฉากได้) ที่คาดว่าจะไม่เปลี่ยนแปลงเมื่อผู้ใช้โต้ตอบกับแอป (สำหรับผู้พิถีพิถันในเรื่องนี้ ใช่ แน่ใจว่าสามารถเปลี่ยนแปลงได้ แต่ประเด็นคือ เมื่อเทียบกับส่วนที่เหลือ ของแอปพลิเคชัน เป็นแบบคงที่จริง) แต่โปรดสังเกตคำสั่ง console.log()
นี่จะเป็นเงื่อนงำของเราที่จะรู้ว่าองค์ประกอบนั้นได้รับการแสดงผล (โปรดจำไว้ว่า "แสดงผล" หมายถึงข้อมูลถูกรวบรวมและคำนวณ / เปรียบเทียบและไม่ใช่ว่าถูกทาสีลงบน DOM จริง)
ดังนั้น ในระหว่างการทดสอบ หากเราไม่เห็นข้อความดังกล่าวในคอนโซลเบราว์เซอร์ แสดงว่าคอมโพเนนต์ของเราไม่ได้แสดงผลเลย หากเราเห็นมันปรากฏขึ้น 10 ครั้ง แสดงว่าองค์ประกอบนั้นแสดงผล 10 ครั้ง; และอื่นๆ
และตอนนี้เรามาดูกันว่าองค์ประกอบหลักของเราใช้องค์ประกอบข้อมูลลูกค้านี้อย่างไร:
import React, { useState } from "react"; import "./styles.css"; import { CustomerInfo } from "./CustomerInfo"; export default function App() { const [totalLikes, setTotalLikes] = useState(0); return ( <div className="App"> <div className="LikesCounter"> <p>You have liked us {totalLikes} times so far.</p> <button onClick={() => setTotalLikes(totalLikes + 1)}> Click here to like again! </button> </div> <div className="CustomerInfo"> <CustomerInfo /> </div> </div> ); }
ดังนั้นเราจึงเห็นว่าองค์ประกอบ App
มีการจัดการสถานะภายในผ่าน useState()
สถานะนี้จะนับจำนวนครั้งที่ผู้ใช้ชอบบริการ/ไซต์ และตั้งค่าเริ่มต้นเป็นศูนย์ ไม่มีอะไรท้าทายเท่าแอป React ใช่ไหม? ในด้าน UI สิ่งต่าง ๆ จะมีลักษณะดังนี้:

ปุ่มดูเย้ายวนเกินกว่าจะไม่ถูกทุบ อย่างน้อยสำหรับฉัน! แต่ก่อนที่จะทำอย่างนั้น ฉันจะเปิดคอนโซลผู้พัฒนาของเบราว์เซอร์และล้างมัน หลังจากนั้น ฉันจะทุบปุ่มสองสามครั้ง และนี่คือสิ่งที่ฉันเห็น:

ฉันกดปุ่ม 19 ครั้ง และตามที่คาดไว้ จำนวนไลค์ทั้งหมดอยู่ที่ 19 รายการ โทนสีทำให้อ่านยากจริงๆ ดังนั้นฉันจึงเพิ่มกล่องสีแดงเพื่อเน้นสิ่งสำคัญ: องค์ประกอบ <CustomerInfo />
แสดงผล 20 ครั้ง!
ทำไมต้อง 20?
ครั้งหนึ่งเมื่อทุกอย่างถูกเรนเดอร์ในตอนแรก และ 19 ครั้งเมื่อกดปุ่ม ปุ่มจะเปลี่ยน totalLikes
ซึ่งเป็นสถานะภายในคอมโพเนนต์ <App />
และเป็นผลให้ส่วนประกอบหลักแสดงผลใหม่ และดังที่เราได้เรียนรู้ไปในหัวข้อก่อนหน้าของโพสต์นี้ ส่วนประกอบทั้งหมดที่อยู่ในนั้นจะได้รับการแสดงผลใหม่เช่นกัน สิ่งนี้ไม่ต้องการเนื่องจากส่วนประกอบ <CustomerInfo />
ไม่ได้เปลี่ยนแปลงในกระบวนการและยังมีส่วนสนับสนุนในกระบวนการแสดงผล
เราจะป้องกันได้อย่างไร?
ตามหัวเรื่องของส่วนนี้โดยใช้ฟังก์ชัน memo()
เพื่อสร้าง "preserved" หรือสำเนาแคชของคอมโพเนนต์ <CustomerInfo />
ด้วยองค์ประกอบที่บันทึกไว้ React จะดูที่อุปกรณ์ประกอบฉากและเปรียบเทียบกับอุปกรณ์ประกอบฉากก่อนหน้า และหากไม่มีการเปลี่ยนแปลง React จะไม่แยกเอาต์พุต "การแสดงผล" ใหม่ออกจากส่วนประกอบนี้
มาเพิ่มโค้ดบรรทัดนี้ในไฟล์ CustomerInfo.js
ของเรา:
export const MemoizedCustomerInfo = React.memo(CustomerInfo);
ใช่ นั่นคือทั้งหมดที่เราต้องทำ! ถึงเวลาที่จะใช้สิ่งนี้ในองค์ประกอบหลักของเราและดูว่ามีอะไรเปลี่ยนแปลงหรือไม่:
import React, { useState } from "react"; import "./styles.css"; import { MemoizedCustomerInfo } from "./CustomerInfo"; export default function App() { const [totalLikes, setTotalLikes] = useState(0); return ( <div className="App"> <div className="LikesCounter"> <p>You have liked us {totalLikes} times so far.</p> <button onClick={() => setTotalLikes(totalLikes + 1)}> Click here to like again! </button> </div> <div className="CustomerInfo"> <MemoizedCustomerInfo /> </div> </div> ); }
ใช่ มีการเปลี่ยนแปลงเพียงสองบรรทัด แต่ฉันต้องการแสดงองค์ประกอบทั้งหมดอยู่ดี ไม่มีอะไรเปลี่ยนแปลง UI-wise ดังนั้นถ้าฉันใช้เวอร์ชันใหม่เพื่อหมุนและกดปุ่มถูกใจสองสามครั้ง ฉันจะได้รับสิ่งนี้:

แล้วเรามีข้อความคอนโซลกี่ข้อความ?
หนึ่งเดียวเท่านั้น! ซึ่งหมายความว่านอกจากการเรนเดอร์ครั้งแรกแล้ว คอมโพเนนต์ไม่ได้ถูกแตะเลย ลองนึกภาพประสิทธิภาพที่เพิ่มขึ้นในแอพระดับสูงจริงๆ! โอเค โอเค ลิงก์ไปยัง Code Playground ที่ฉันสัญญาไว้อยู่ที่นี่แล้ว หากต้องการทำซ้ำตัวอย่างก่อนหน้านี้ คุณจะต้องนำเข้าและใช้ CustomerInfo
แทน MemoizedCustomerInfo
จาก CustomerInfo.js
ที่กล่าวว่า memo()
ไม่ใช่ทรายวิเศษที่คุณสามารถโรยได้ทุกที่และคาดหวังผลลัพธ์ที่น่าอัศจรรย์ การใช้ memo()
มากเกินไปอาจทำให้เกิดข้อบกพร่องที่ยุ่งยากในแอปของคุณและบางครั้งอาจทำให้การอัปเดตที่คาดหวังบางอย่างล้มเหลว คำแนะนำทั่วไปเกี่ยวกับการเพิ่มประสิทธิภาพ "ก่อนกำหนด" ก็นำมาใช้ที่นี่เช่นกัน ขั้นแรก สร้างแอปของคุณตามสัญชาตญาณของคุณ จากนั้นทำโปรไฟล์อย่างละเอียดเพื่อดูว่าส่วนใดช้าและหากปรากฏว่าส่วนประกอบที่บันทึกเป็นวิธีแก้ปัญหาที่ถูกต้อง ให้แนะนำสิ่งนี้เท่านั้น
การออกแบบส่วนประกอบ “อัจฉริยะ”
ฉันใส่คำว่า "ฉลาด" ไว้ในเครื่องหมายคำพูดเพราะ: 1) ความฉลาดเป็นเรื่องส่วนตัวและตามสถานการณ์ 2) การกระทำที่ชาญฉลาดที่สันนิษฐานว่ามักมีผลที่ไม่พึงประสงค์ ดังนั้น คำแนะนำของฉันสำหรับส่วนนี้คือ: อย่ามั่นใจมากเกินไปในสิ่งที่คุณกำลังทำ
ด้วยวิธีดังกล่าว ความเป็นไปได้อย่างหนึ่งในการปรับปรุงประสิทธิภาพการเรนเดอร์คือการออกแบบและจัดวางส่วนประกอบให้แตกต่างกันเล็กน้อย ตัวอย่างเช่น คอมโพเนนต์ย่อยสามารถจัดองค์ประกอบใหม่และย้ายไปยังตำแหน่งอื่นในลำดับชั้นเพื่อหลีกเลี่ยงการแสดงผลซ้ำ ไม่มีกฎเกณฑ์ใดที่ระบุว่า "องค์ประกอบ ChatPhotoView ต้องอยู่ภายในองค์ประกอบ Chat เสมอ" ในกรณีพิเศษ (และกรณีเหล่านี้คือกรณีที่เรามีหลักฐานสนับสนุนว่าประสิทธิภาพได้รับผลกระทบ) การบิดเบือน/ทำลายกฎอาจเป็นความคิดที่ดี
บทสรุป
สามารถทำได้มากขึ้นเพื่อเพิ่มประสิทธิภาพแอป React โดยทั่วไป แต่เนื่องจากบทความนี้เกี่ยวกับการแสดงผล ฉันจึงจำกัดขอบเขตของการสนทนา ฉันหวังว่าตอนนี้คุณจะมีข้อมูลเชิงลึกที่ดีขึ้นเกี่ยวกับสิ่งที่เกิดขึ้นใน React ภายใต้ประทุน การเรนเดอร์ที่แท้จริงคืออะไร และมันจะส่งผลต่อประสิทธิภาพของแอปพลิเคชันอย่างไร
ต่อไป มาทำความเข้าใจว่า React Hooks คืออะไร?