ใช้ React.memo() ให้ฉลาด
Jun 7, 2022

หลายคนที่เขียน React คงรู้จักตัว HOC ชื่อ React.memo()
ในการทำ Memoization ให้กับ Functional Component กันอยู่แล้ว เพื่อไม่ให้ Component นั้นถูก re-render โดยไม่จำเป็น แต่บางคนอาจจะสงสัยว่าทำไม React ไม่ทำ Memoization ให้ทุก Component ไปเลยโดยจำเป็นต้องมีตัว React.memo() แยก หรือทำไมเราไม่ใช้ React Memo กับทุก Component ไปเลยล่ะ ? ในบล็อกนี้เราจะพูดถึงกรณีที่ควรใช้ React Memo และกรณีที่ไม่ควรใช้ React Memo กันครับ
ทวนเรื่อง Memoization
หลักการ Memoization นั้นเป็นเทคนิคการทำ Optimization ให้โปรแกรมของเราเพื่อให้โปรแกรมทำงานได้อย่างมีประสิทธิภาพและเร็วขึ้น ด้วยการเก็บผลการคำนวณ (Computation results) ไว้ใน cache และดึงผลการคำนวณนี้ออกมาใช้ในครั้งต่อไปที่มีการคำนวณแบบเดิมแทนที่จะต้องคำนวณใหม่
ตัวอย่างการทำ Memoization ในภาษา Javascript
สมมุติเราต้องการหาตัวเลขลำดับที่ nth ใน Fibonacci Sequence เราสามารถเขียน Recursive function ได้ตามนี้
สมมุติเราต้องการหาตัวเลขลำดับที่ 5 โดยการเรียก fib(5)
ตัวฟังก์ชั่นจะถูกเรียกแบบ recursive ดังแผนภาพนี้

จะสังเกตุได้ว่า fib(0), fib(1), fib(2) and fib(3)
ถูกเรียกอยู่หลายครั้ง ทำให้เกิดการคำนวณซ้ำแบบเดิมโดยไม่จำเป็น เราสามารถแก้ปัญหานี้ได้ด้วยการทำ Memoization โดยเปลี่ยนการ implement ตัวฟังก์ชั่น fib เป็นดังนี้
สังเกตได้ว่าเราจะมีการเพิ่ม memo object ขึ้นมาเพื่อเป็น cache และเมื่อเจอลำดับที่ n ซ้ำ แปลว่า memo[n]
ได้ถูกคำนวณไปแล้ว เราก็จะ return ค่าที่เก็บเอาไว้เลยโดยไม่ต้องคำนวณอีกรอบ
หลักการของ React.memo()
React.memo() เป็น Higher order component (HOC)
โดย Component จะถูก re-render ก็ต่อเมื่อ props มีการเปลี่ยนแปลง นั่นหมายความว่าผลลัพธ์ของการ render ครั้งก่อนจะถูก memonize ไว้และแสดงผลเดิม ถ้า props เหมือนเดิม ซึ่งช่วยในเรื่อง Performance Optimization
ซึ่งการเปรียบเทียบ props ล่าสุดกับ props ก่อนหน้านั้น React จะจัดการให้อัตโนมัติ แต่เราก็สามารถเขียน Compare function ได้เองถ้าต้องการ (เช่น การทำ deep object comparison)
จะเห็นว่า React.memo()
รับ argument ที่ 2 เป็น Compare function ที่ return ค่า boolean ระบุว่า props ล่าสุดกับ props ก่อนหน้าเหมือนกันหรือไม่
เมื่อไหร่ควรใช้ React.memo()
เราควรใช้ React Memo ก็ต่อเมื่อ Component นั้นถูก re-render บ่อย ๆ ด้วย props เดิมซ้ำ ๆ ซึ่งจะเกิดได้บ่อยจากการที่ Component นั้นเป็น Child ของอีก Component นึงที่มีการ re-render บ่อย แต่ส่ง props เดิมให้ Child
ตัวอย่างเช่น สมมุติให้เรามี Movie Component
ซึ่งถูก render เป็น Child ของ MovieViewsRealtime Component
โดยสมมุติให้ MovieViewsRealtime นั้นรับ prop ชื่อ views ซึ่งเป็นค่าที่ถูก fetch มาจาก api ทุก ๆ วินาที
ซึ่งทุก ๆ ครั้งที่ค่าของ views เปลี่ยน แน่นอนว่า MovieViewsRealtime นั้นจะถูก re-render แต่นั่นทำให้ Movie ถูก re-render ตามไปด้วยเพราะเป็น Child ถึงแม้ props ของ Movie จะไม่เปลี่ยนก็ตาม
กรณีนี้จึงควรใช้ React.memo() ที่ตัว Movie Component
กรณีนี้ตัว MemoizedMovie จะถูก re-render ก็ต่อเมื่อ props ของมันเปลี่ยนเท่านั้น (title, releaseDate)
เมื่อไหร่ไม่ควรใช้ React.memo()
กรณีนี้ที่ไม่ควรใช้ React Memo นั้นคือกรณีที่ Component นั้นถูก re-render บ่อย ๆ ด้วย prop ไม่ซ้ำกัน สมมุติให้ MemoizedMovie รับ views prop ด้วย
กรณีนี้ MemoizedMovie จะถูก re-render ทุกครั้งที่ views เปลี่ยน ซึ่งทำให้ไม่มีประโยชน์ที่ใช้ React Memo แถมยังทำให้ Performance แย่ลงอีกด้วยเพราะจะเกิด Overhead ที่ Compare function ที่เปรียบเทียบ props ทุกครั้งบ่อย ๆ โดยผลลัพธ์ออกมาว่าต้อง re-render ตลอดอีกด้วย
สรุป
สรุปการใช้ React Memo ได้จาก Rule of thumb ที่ว่า
Don't use memoization if you can't quantify the performance gains.
คืออย่าใช้ React Memo ไปหมดทุก Component ถ้าลองแล้วไม่ได้ช่วยให้ Performace ดีขึ้น เพราะนอกจากจะไม่ดีขึ้นแล้วยังทำให้ Performance ตกอีกด้วย
อ้างอิง
https://www.freecodecamp.org/news/memoization-in-javascript-and-react/
https://reactjs.org/docs/react-api.html#reactmemo
https://dmitripavlutin.com/use-react-memo-wisely