ไทย

สำรวจเทคนิค React memoization ขั้นสูงเพื่อเพิ่มประสิทธิภาพในแอปพลิเคชันระดับโลก เรียนรู้ว่าเมื่อใดและอย่างไรที่ควรใช้ React.memo, useCallback, useMemo และอื่น ๆ เพื่อสร้าง UI ที่มีประสิทธิภาพ

React Memo: เจาะลึกเทคนิคการปรับปรุงประสิทธิภาพสำหรับแอปพลิเคชันระดับโลก

React เป็นไลบรารี JavaScript ที่ทรงพลังสำหรับการสร้างส่วนติดต่อผู้ใช้ (user interfaces) แต่เมื่อแอปพลิเคชันมีความซับซ้อนมากขึ้น การปรับปรุงประสิทธิภาพ (performance optimization) ก็กลายเป็นสิ่งสำคัญ เครื่องมือที่จำเป็นอย่างหนึ่งในชุดเครื่องมือการปรับปรุงประสิทธิภาพของ React คือ React.memo บล็อกโพสต์นี้จะให้คำแนะนำที่ครอบคลุมเพื่อทำความเข้าใจและใช้งาน React.memo และเทคนิคที่เกี่ยวข้องอย่างมีประสิทธิภาพ เพื่อสร้างแอปพลิเคชัน React ที่มีประสิทธิภาพสูงสำหรับผู้ใช้ทั่วโลก

React.memo คืออะไร?

React.memo คือ higher-order component (HOC) ที่ทำการ memoize ให้กับ functional component พูดง่ายๆ ก็คือ มันจะป้องกันไม่ให้ component ทำการ re-render หาก props ของมันไม่มีการเปลี่ยนแปลง โดยค่าเริ่มต้น มันจะทำการเปรียบเทียบ props แบบตื้น (shallow comparison) ซึ่งสามารถปรับปรุงประสิทธิภาพได้อย่างมาก โดยเฉพาะสำหรับ component ที่มีการเรนเดอร์ที่ใช้การคำนวณสูง หรือ re-render บ่อยครั้งแม้ว่า props จะยังคงเหมือนเดิมก็ตาม

ลองนึกภาพ component ที่แสดงโปรไฟล์ของผู้ใช้ หากข้อมูลของผู้ใช้ (เช่น ชื่อ, รูปโปรไฟล์) ไม่มีการเปลี่ยนแปลง ก็ไม่จำเป็นต้อง re-render component นั้นใหม่ React.memo ช่วยให้คุณสามารถข้ามการ re-render ที่ไม่จำเป็นนี้ไปได้ ซึ่งช่วยประหยัดเวลาในการประมวลผลอันมีค่า

ทำไมต้องใช้ React.memo?

นี่คือประโยชน์หลักของการใช้ React.memo:

การใช้งาน React.memo ขั้นพื้นฐาน

การใช้ React.memo นั้นตรงไปตรงมา เพียงแค่ห่อ functional component ของคุณด้วยมัน:

import React from 'react';

const MyComponent = (props) => {
 console.log('MyComponent rendered');
 return (
 
{props.data}
); }; export default React.memo(MyComponent);

ในตัวอย่างนี้ MyComponent จะ re-render ก็ต่อเมื่อ prop ที่ชื่อ data เปลี่ยนแปลงเท่านั้น คำสั่ง console.log จะช่วยให้คุณตรวจสอบได้ว่า component re-render จริงเมื่อใด

การทำความเข้าใจ Shallow Comparison

โดยค่าเริ่มต้น React.memo จะทำการเปรียบเทียบ props แบบตื้น (shallow comparison) ซึ่งหมายความว่ามันจะตรวจสอบว่า reference ของ props เปลี่ยนแปลงไปหรือไม่ ไม่ใช่ค่าที่อยู่ภายใน สิ่งนี้สำคัญที่ต้องทำความเข้าใจเมื่อต้องจัดการกับ object และ array

พิจารณาตัวอย่างต่อไปนี้:

import React, { useState } from 'react';

const MyComponent = (props) => {
 console.log('MyComponent rendered');
 return (
 
{props.data.name}
); }; const MemoizedComponent = React.memo(MyComponent); const App = () => { const [user, setUser] = useState({ name: 'John', age: 30 }); const handleClick = () => { setUser({ ...user }); // สร้าง object ใหม่ที่มีค่าเหมือนเดิม }; return (
); }; export default App;

ในกรณีนี้ ถึงแม้ว่าค่าใน object user (name และ age) จะยังคงเหมือนเดิม แต่ฟังก์ชัน handleClick จะสร้าง object reference ใหม่ทุกครั้งที่ถูกเรียก ดังนั้น React.memo จะเห็นว่า prop data มีการเปลี่ยนแปลง (เพราะ object reference แตกต่างกัน) และจะทำการ re-render MyComponent

ฟังก์ชันเปรียบเทียบแบบกำหนดเอง (Custom Comparison Function)

เพื่อจัดการกับปัญหาของการเปรียบเทียบแบบตื้นกับ object และ array React.memo อนุญาตให้คุณส่งฟังก์ชันเปรียบเทียบแบบกำหนดเองเป็นอาร์กิวเมนต์ที่สองได้ ฟังก์ชันนี้รับอาร์กิวเมนต์สองตัวคือ prevProps และ nextProps มันควรจะคืนค่า true หาก component *ไม่ควร* re-render (กล่าวคือ props มีค่าเทียบเท่ากัน) และคืนค่า false หากควร re-render

นี่คือวิธีที่คุณสามารถใช้ฟังก์ชันเปรียบเทียบแบบกำหนดเองในตัวอย่างก่อนหน้านี้:

import React, { useState, memo } from 'react';

const MyComponent = (props) => {
 console.log('MyComponent rendered');
 return (
 
{props.data.name}
); }; const areEqual = (prevProps, nextProps) => { return prevProps.data.name === nextProps.data.name && prevProps.data.age === nextProps.data.age; }; const MemoizedComponent = memo(MyComponent, areEqual); const App = () => { const [user, setUser] = useState({ name: 'John', age: 30 }); const handleClick = () => { setUser({ ...user }); }; return (
); }; export default App;

ในตัวอย่างที่อัปเดตนี้ ฟังก์ชัน areEqual จะเปรียบเทียบ property name และ age ของ object user ตอนนี้ MemoizedComponent จะ re-render ก็ต่อเมื่อ name หรือ age เปลี่ยนแปลงเท่านั้น

เมื่อใดที่ควรใช้ React.memo

React.memo มีประสิทธิภาพสูงสุดในสถานการณ์ต่อไปนี้:

อย่างไรก็ตาม สิ่งสำคัญที่ต้องทราบคือ React.memo ไม่ใช่ยาวิเศษ การใช้งานอย่างไม่บันยะบันยังอาจส่งผลเสียต่อประสิทธิภาพได้ เพราะการเปรียบเทียบแบบตื้นเองก็มีค่าใช้จ่าย ดังนั้นจึงเป็นเรื่องสำคัญที่จะต้องทำการ profile แอปพลิเคชันของคุณและระบุ component ที่จะได้รับประโยชน์สูงสุดจากการทำ memoization

ทางเลือกอื่นนอกเหนือจาก React.memo

แม้ว่า React.memo จะเป็นเครื่องมือที่ทรงพลัง แต่ก็ไม่ใช่ทางเลือกเดียวสำหรับการปรับปรุงประสิทธิภาพของ component ใน React นี่คือทางเลือกและเทคนิคเสริมบางอย่าง:

1. PureComponent

สำหรับ class components PureComponent ให้ฟังก์ชันการทำงานที่คล้ายกับ React.memo โดยมันจะทำการเปรียบเทียบแบบตื้นทั้ง props และ state และจะ re-render ก็ต่อเมื่อมีการเปลี่ยนแปลงเท่านั้น

import React from 'react';

class MyComponent extends React.PureComponent {
 render() {
 console.log('MyComponent rendered');
 return (
 
{this.props.data}
); } } export default MyComponent;

PureComponent เป็นทางเลือกที่สะดวกสบายแทนการเขียน shouldComponentUpdate ด้วยตนเอง ซึ่งเป็นวิธีดั้งเดิมในการป้องกันการ re-render ที่ไม่จำเป็นใน class components

2. shouldComponentUpdate

shouldComponentUpdate เป็น lifecycle method ใน class components ที่ช่วยให้คุณสามารถกำหนดตรรกะเองได้ว่าจะให้ component re-render หรือไม่ มันให้ความยืดหยุ่นสูงสุด แต่ก็ต้องใช้ความพยายามด้วยตนเองมากขึ้น

import React from 'react';

class MyComponent extends React.Component {
 shouldComponentUpdate(nextProps, nextState) {
 return nextProps.data !== this.props.data;
 }

 render() {
 console.log('MyComponent rendered');
 return (
 
{this.props.data}
); } } export default MyComponent;

แม้ว่า shouldComponentUpdate จะยังคงใช้งานได้ แต่โดยทั่วไปแล้ว PureComponent และ React.memo มักเป็นที่นิยมมากกว่าเนื่องจากความเรียบง่ายและใช้งานง่าย

3. useCallback

useCallback คือ React hook ที่ทำการ memoize ให้กับฟังก์ชัน มันจะคืนค่าฟังก์ชันเวอร์ชันที่ถูก memoize ซึ่งจะเปลี่ยนแปลงก็ต่อเมื่อ dependency ตัวใดตัวหนึ่งของมันเปลี่ยนแปลงไปเท่านั้น สิ่งนี้มีประโยชน์อย่างยิ่งสำหรับการส่ง callbacks เป็น props ไปยัง memoized components

พิจารณาตัวอย่างต่อไปนี้:

import React, { useState, useCallback, memo } from 'react';

const MyComponent = (props) => {
 console.log('MyComponent rendered');
 return (
 
 );
};

const MemoizedComponent = memo(MyComponent);

const App = () => {
 const [count, setCount] = useState(0);

 const handleClick = useCallback(() => {
 setCount(count + 1);
 }, [count]);

 return (
 

Count: {count}

); }; export default App;

ในตัวอย่างนี้ useCallback ช่วยให้มั่นใจได้ว่าฟังก์ชัน handleClick จะเปลี่ยนแปลงก็ต่อเมื่อ state count เปลี่ยนแปลงเท่านั้น หากไม่มี useCallback ฟังก์ชันใหม่จะถูกสร้างขึ้นทุกครั้งที่ App re-render ซึ่งทำให้ MemoizedComponent re-render โดยไม่จำเป็น

4. useMemo

useMemo คือ React hook ที่ทำการ memoize ค่า มันจะคืนค่าที่ถูก memoize ซึ่งจะเปลี่ยนแปลงก็ต่อเมื่อ dependency ตัวใดตัวหนึ่งของมันเปลี่ยนแปลงไปเท่านั้น สิ่งนี้มีประโยชน์สำหรับการหลีกเลี่ยงการคำนวณที่มีค่าใช้จ่ายสูงซึ่งไม่จำเป็นต้องทำงานใหม่ทุกครั้งที่ re-render

import React, { useState, useMemo } from 'react';

const App = () => {
 const [input, setInput] = useState('');

 const expensiveCalculation = (str) => {
 console.log('Calculating...');
 let result = 0;
 for (let i = 0; i < str.length * 1000000; i++) {
 result++;
 }
 return result;
 };

 const memoizedResult = useMemo(() => expensiveCalculation(input), [input]);

 return (
 
setInput(e.target.value)} />

Result: {memoizedResult}

); }; export default App;

ในตัวอย่างนี้ useMemo ช่วยให้มั่นใจได้ว่าฟังก์ชัน expensiveCalculation จะถูกเรียกใช้ก็ต่อเมื่อ state input เปลี่ยนแปลงเท่านั้น สิ่งนี้ช่วยป้องกันไม่ให้การคำนวณทำงานซ้ำทุกครั้งที่ re-render ซึ่งสามารถปรับปรุงประสิทธิภาพได้อย่างมาก

ตัวอย่างการใช้งานจริงสำหรับแอปพลิเคชันระดับโลก

ลองพิจารณาตัวอย่างการใช้งานจริงบางอย่างว่า React.memo และเทคนิคที่เกี่ยวข้องสามารถนำไปใช้ในแอปพลิเคชันระดับโลกได้อย่างไร:

1. ตัวเลือกภาษา (Language Selector)

component ตัวเลือกภาษามักจะเรนเดอร์รายการภาษาที่มีอยู่ รายการนี้อาจค่อนข้างคงที่ หมายความว่าไม่เปลี่ยนแปลงบ่อยนัก การใช้ React.memo สามารถป้องกันไม่ให้ตัวเลือกภาษา re-render โดยไม่จำเป็นเมื่อส่วนอื่น ๆ ของแอปพลิเคชันอัปเดต

import React, { memo } from 'react';

const LanguageItem = ({ language, onSelect }) => {
 console.log(`LanguageItem ${language} rendered`);
 return (
 
  • onSelect(language)}>{language}
  • ); }; const MemoizedLanguageItem = memo(LanguageItem); const LanguageSelector = ({ languages, onSelect }) => { return (
      {languages.map((language) => ( ))}
    ); }; export default LanguageSelector;

    ในตัวอย่างนี้ MemoizedLanguageItem จะ re-render ก็ต่อเมื่อ prop language หรือ onSelect เปลี่ยนแปลงเท่านั้น ซึ่งจะเป็นประโยชน์อย่างยิ่งหากรายการภาษามีความยาวหรือถ้า handler onSelect มีความซับซ้อน

    2. ตัวแปลงสกุลเงิน (Currency Converter)

    component ตัวแปลงสกุลเงินอาจแสดงรายการสกุลเงินและอัตราแลกเปลี่ยน อัตราแลกเปลี่ยนอาจมีการอัปเดตเป็นระยะ แต่รายการสกุลเงินอาจค่อนข้างคงที่ การใช้ React.memo สามารถป้องกันไม่ให้รายการสกุลเงิน re-render โดยไม่จำเป็นเมื่ออัตราแลกเปลี่ยนอัปเดต

    import React, { memo } from 'react';
    
    const CurrencyItem = ({ currency, rate, onSelect }) => {
     console.log(`CurrencyItem ${currency} rendered`);
     return (
     
  • onSelect(currency)}>{currency} - {rate}
  • ); }; const MemoizedCurrencyItem = memo(CurrencyItem); const CurrencyConverter = ({ currencies, onSelect }) => { return (
      {Object.entries(currencies).map(([currency, rate]) => ( ))}
    ); }; export default CurrencyConverter;

    ในตัวอย่างนี้ MemoizedCurrencyItem จะ re-render ก็ต่อเมื่อ prop currency, rate หรือ onSelect เปลี่ยนแปลงเท่านั้น สิ่งนี้สามารถปรับปรุงประสิทธิภาพได้หากรายการสกุลเงินมีความยาวหรือถ้าการอัปเดตอัตราแลกเปลี่ยนเกิดขึ้นบ่อยครั้ง

    3. การแสดงโปรไฟล์ผู้ใช้ (User Profile Display)

    การแสดงโปรไฟล์ผู้ใช้เกี่ยวข้องกับการแสดงข้อมูลคงที่ เช่น ชื่อ รูปโปรไฟล์ และอาจรวมถึงประวัติส่วนตัว การใช้ `React.memo` ช่วยให้มั่นใจได้ว่า component จะ re-render ก็ต่อเมื่อข้อมูลผู้ใช้เปลี่ยนแปลงจริง ๆ ไม่ใช่ทุกครั้งที่ parent component อัปเดต

    import React, { memo } from 'react';
    
    const UserProfile = ({ user }) => {
     console.log('UserProfile rendered');
     return (
     

    {user.name}

    Profile

    {user.bio}

    ); }; export default memo(UserProfile);

    สิ่งนี้มีประโยชน์อย่างยิ่งหาก UserProfile เป็นส่วนหนึ่งของแดชบอร์ดหรือแอปพลิเคชันขนาดใหญ่ที่มีการอัปเดตบ่อยครั้ง แต่ข้อมูลผู้ใช้เองไม่ได้เปลี่ยนแปลงบ่อยนัก

    ข้อผิดพลาดที่พบบ่อยและวิธีหลีกเลี่ยง

    แม้ว่า React.memo จะเป็นเครื่องมือเพิ่มประสิทธิภาพที่มีคุณค่า แต่สิ่งสำคัญคือต้องระวังข้อผิดพลาดที่พบบ่อยและวิธีหลีกเลี่ยง:

    การทำโปรไฟล์แอปพลิเคชันของคุณ (Profiling Your Application)

    วิธีที่ดีที่สุดในการพิจารณาว่า React.memo ช่วยปรับปรุงประสิทธิภาพได้จริงหรือไม่ คือการทำโปรไฟล์แอปพลิเคชันของคุณ React มีเครื่องมือหลายอย่างสำหรับการทำโปรไฟล์ รวมถึง React DevTools Profiler และ React.Profiler API

    React DevTools Profiler ช่วยให้คุณสามารถบันทึกร่องรอยประสิทธิภาพ (performance traces) ของแอปพลิเคชันและระบุ component ที่ re-render บ่อยครั้ง ส่วน React.Profiler API ช่วยให้คุณสามารถวัดเวลาในการเรนเดอร์ของ component ที่ต้องการได้โดยตรงผ่านโค้ด

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

    บทสรุป

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

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

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

    แหล่งข้อมูลเพิ่มเติม