در این آموزش، نحوه پیاده سازی حافظه را در React می آموزیم. حافظه‌گذاری با ذخیره کردن نتایج فراخوانی‌های عملکرد گران قیمت و برگرداندن آن نتایج ذخیره‌شده در حافظه پنهان در صورت نیاز، عملکرد را بهبود می‌بخشد.

موارد زیر را پوشش خواهیم داد:

  • چگونه React UI را رندر می کند
  • چرا نیاز به یادداشت React وجود دارد
  • چگونه می‌توانیم حافظه‌گذاری را برای اجزای تابعی و کلاسی پیاده‌سازی کنیم
  • مواردی که باید در مورد حفظ کردن در ذهن داشته باشید

این مقاله فرض می کند که شما درک اولیه ای از مولفه های کلاس و عملکرد در React دارید. اگر می‌خواهید در مورد آن موضوعات صحبت کنید، اسناد رسمی React را بررسی کنید قطعات و لوازم جانبی.

یادداشت در React

How React رابط کاربری را رندر می کند

قبل از پرداختن به جزئیات حفظ کردن در React، اجازه دهید ابتدا نگاهی بیندازیم که چگونه React UI را با استفاده از DOM مجازی ارائه می‌کند.

DOM معمولی اساساً شامل مجموعه ای از گره ها است که به صورت درخت نمایش داده می شوند. هر گره در DOM نمایشی از یک عنصر UI است. هر زمان که تغییر حالتی در برنامه شما ایجاد می شود، گره مربوطه برای آن عنصر UI و همه فرزندان آن در DOM به روز می شود و سپس UI دوباره رنگ می شود تا تغییرات به روز شده را منعکس کند.

به‌روزرسانی گره‌ها با کمک الگوریتم‌های درختی کارآمد سریع‌تر است، اما رنگ‌آمیزی مجدد کند است و زمانی که DOM دارای تعداد زیادی عناصر UI باشد، می‌تواند تأثیری بر عملکرد داشته باشد. بنابراین DOM مجازی در React معرفی شد.

این یک نمایش مجازی از DOM واقعی است. اکنون، هر زمان که تغییری در وضعیت برنامه ایجاد شود، به جای به‌روزرسانی مستقیم DOM واقعی، React یک DOM مجازی جدید ایجاد می‌کند. سپس React این DOM مجازی جدید را با DOM مجازی ایجاد شده قبلی مقایسه می کند تا تفاوت هایی را که نیاز به رنگ آمیزی مجدد دارند را بیابد.

با استفاده از این تفاوت ها، DOM مجازی DOM واقعی را به طور موثر با تغییرات به روز می کند. این عملکرد را بهبود می بخشد، زیرا به جای به روز رسانی ساده عنصر UI و همه فرزندان آن، DOM مجازی به طور موثر فقط تغییرات ضروری و حداقل را در DOM واقعی به روز می کند.

چرا در React به یادداشت نیاز داریم؟

در بخش قبل، دیدیم که چگونه React به‌روزرسانی‌های DOM را با استفاده از DOM مجازی برای بهبود عملکرد به‌طور مؤثر انجام می‌دهد. در این بخش، یک مورد استفاده را بررسی خواهیم کرد که نیاز به یادداشت برای افزایش عملکرد بیشتر را توضیح می دهد.

ما یک کلاس والد ایجاد می کنیم که حاوی دکمه ای برای افزایش متغیر حالت به نام است count. کامپوننت والد همچنین دارای یک فراخوانی به مولفه فرزند است که یک پایه به آن ارسال می کند. ما نیز اضافه کرده ایم console.log() دستورات در رندر متد هر دو کلاس:


class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  handleClick = () => {
    this.setState((prevState) => {
      return { count: prevState.count + 1 };
    });
  };

  render() {
    console.log("Parent render");
    return (
      <div className="App">
        <button onClick={this.handleClick}>Increment</button>
        <h2>{this.state.count}</h2>
        <Child name={"joe"} />
      </div>
    );
  }
}

export default Parent;

کد کامل این مثال در سایت موجود است CodeSandbox.

ما یک را ایجاد خواهیم کرد Child کلاسی که یک prop ارسال شده توسط مولفه والد را می پذیرد و آن را در UI نمایش می دهد:


class Child extends React.Component {
  render() {
    console.log("Child render");
    return (
      <div>
        <h2>{this.props.name}</h2>
      </div>
    );
  }
}

export default Child;

هر زمان که روی دکمه در مولفه والد کلیک می کنیم، مقدار شمارش تغییر می کند. از آنجایی که این یک تغییر حالت است، متد رندر مولفه والد فراخوانی می شود.

پروپوزال‌های ارسال شده به کلاس فرزند برای هر رندر مجدد والدین یکسان می‌ماند، بنابراین مؤلفه فرزند نباید دوباره رندر شود. با این حال، هنگامی که کد بالا را اجرا می کنیم و تعداد را افزایش می دهیم، خروجی زیر را دریافت می کنیم:

Parent render
Child render
Parent render
Child render
Parent render
Child render

شما می توانید تعداد مثال بالا را خودتان در جعبه شنی زیر افزایش دهید و کنسول را برای خروجی ببینید:


از این خروجی، می‌توانیم ببینیم که وقتی مؤلفه والد دوباره رندر می‌شود، مؤلفه فرزند را نیز دوباره رندر می‌کند – حتی زمانی که لوازم ارسال شده به مؤلفه فرزند بدون تغییر باشند. این باعث می شود که DOM مجازی کودک یک بررسی تفاوت با DOM مجازی قبلی انجام دهد. از آنجایی که ما هیچ تفاوتی در مؤلفه فرزند نداریم – از آنجایی که قطعات برای همه رندرها یکسان هستند – DOM واقعی به روز نمی شود.

ما یک مزیت عملکرد داریم که در آن DOM واقعی بی جهت به‌روزرسانی نمی‌شود، اما می‌توانیم اینجا ببینیم که، حتی زمانی که هیچ تغییر واقعی در مؤلفه فرزند وجود نداشت، DOM مجازی جدید ایجاد شد و بررسی تفاوت انجام شد. برای اجزای کوچک React، این عملکرد ناچیز است، اما برای قطعات بزرگ، تأثیر عملکرد قابل توجه است. برای جلوگیری از این رندر مجدد و بررسی DOM مجازی، از حافظه گذاری استفاده می کنیم.

یادداشت در React

در زمینه یک برنامه React، حافظه‌سازی تکنیکی است که در آن، هر زمان که مؤلفه والد دوباره رندر می‌شود، مؤلفه فرزند تنها در صورتی که تغییری در لوازم جانبی ایجاد شود، دوباره رندر می‌شود. اگر تغییری در props ایجاد نشود، روش رندر را اجرا نمی‌کند و نتیجه ذخیره‌شده را برمی‌گرداند. از آنجایی که روش رندر اجرا نمی شود، ایجاد DOM مجازی و بررسی تفاوت وجود نخواهد داشت – بنابراین عملکرد ما را تقویت می کند.

حال، بیایید ببینیم که چگونه می توان حافظه را در اجزای کلاس و عملکردی React پیاده سازی کرد تا از این رندر مجدد غیر ضروری جلوگیری شود.

پیاده سازی حافظه در یک جزء کلاس

برای پیاده سازی حافظه در یک جزء کلاس، ما استفاده خواهیم کرد React.PureComponent. React.PureComponent اجرا می کند shouldComponentUpdate()، که یک مقایسه سطحی در حالت و props انجام می دهد و مولفه React را تنها در صورت تغییر در props یا وضعیت ارائه می دهد.

مولفه فرزند را به کد زیر تغییر دهید:


class Child extends React.PureComponent { 
  render() {
    console.log("Child render");
    return (
      <div>
        <h2>{this.props.name}</h2>
      </div>
    );
  }
}

export default Child;

کد کامل این مثال در جعبه شنی زیر نشان داده شده است:


جزء اصلی بدون تغییر باقی می ماند. حال، وقتی تعداد مولفه والد را افزایش می‌دهیم، خروجی در کنسول به صورت زیر است:

Parent render
Child render
Parent render
Parent render

برای اولین رندر، هم متد رندر مولفه والد و هم فرزند را فراخوانی می کند.

برای رندر مجدد بعدی در هر افزایش، فقط جزء اصلی است render تابع نامیده می شود. مؤلفه فرزند دوباره ارائه نمی شود.

پیاده سازی حافظه در یک جزء عملکردی

برای پیاده سازی حافظه گذاری در کامپوننت های کاربردی React، از آن استفاده خواهیم کرد React.memo().React.memo() یک جزء مرتبه بالاتر (HOC) است که کار مشابهی را انجام می دهد PureComponent، از رندرهای غیرضروری اجتناب کنید.

در زیر کد یک جزء عملکردی آمده است:


export function Child(props) {
  console.log("Child render");
  return (
    <div>
      <h2>{props.name}</h2>
    </div>
  );
}

export default React.memo(Child); 

همانطور که در زیر نشان داده شده است، کامپوننت والد را نیز به یک جزء تابعی تبدیل می کنیم:


export default function Parent() {
  const [count, setCount] = useState(0);
  const handleClick = () => {
    setCount(count + 1);
  };
  console.log("Parent render");
  return (
    <div>
      <button onClick={handleClick}>Increment</button>
      <h2>{count}</h2>
      <Child name={"joe"} />
    </div>
  );
}

کد کامل این مثال را می توانید در سندباکس زیر مشاهده کنید:


حال، وقتی تعداد مولفه والد را افزایش می‌دهیم، خروجی زیر به کنسول داده می‌شود:

Parent render
Child render
Parent render
Parent render
Parent render

مشکل React.memo() برای Function Props

در مثال بالا دیدیم که وقتی از React.memo() HOC برای مؤلفه فرزند، مؤلفه فرزند مجدداً ارائه نمی شود، حتی اگر مؤلفه والد انجام شود.

با این حال، یک اخطار کوچک که باید از آن آگاه بود این است که اگر یک تابع را به‌عنوان تکیه‌گاه به مؤلفه فرزند منتقل کنیم، حتی پس از استفاده React.memo()، مؤلفه فرزند دوباره ارائه می شود. بیایید نمونه ای از این را ببینیم.

ما مولفه والد را مطابق شکل زیر تغییر می دهیم. در اینجا، یک تابع handler اضافه کرده‌ایم که آن را به عنوان props به مؤلفه فرزند می‌فرستیم:


export default function Parent() {
  const [count, setCount] = useState(0);
  const handleClick = () => {
    setCount(count + 1);
  };

  const handler = () => {
    console.log("handler");    
  };

  console.log("Parent render");
  return (
    <div className="App">
      <button onClick={handleClick}>Increment</button>
      <h2>{count}</h2>
      <Child name={"joe"} childFunc={handler} />
    </div>
  );
}

کد مؤلفه فرزند همان طور که هست باقی می ماند. ما از تابعی که به عنوان props در مولفه فرزند ارسال کرده ایم استفاده نمی کنیم:


export function Child(props) {
  console.log("Child render");
  return (
    <div>
      <h2>{props.name}</h2>
    </div>
  );
}

export default React.memo(Child);

اکنون، وقتی تعداد را در مؤلفه والد افزایش می‌دهیم، مولفه فرزند را مجدداً رندر می‌کند و همچنین مؤلفه فرزند را دوباره رندر می‌کند، حتی اگر هیچ تغییری در پروپوزال‌های ارسال شده وجود نداشته باشد.

بنابراین، چه چیزی باعث شد که کودک دوباره ارائه شود؟ پاسخ این است که، هر بار که مولفه والد دوباره ارائه می شود، یک تابع کنترل کننده جدید ایجاد می شود و به فرزند ارسال می شود. اکنون، از آنجایی که تابع handler در هر رندر مجدد ایجاد می‌شود، فرزند با مقایسه سطحی از props، متوجه می‌شود که مرجع کنترل‌کننده تغییر کرده است و مؤلفه فرزند را دوباره رندر می‌کند.

در بخش بعدی نحوه رفع این مشکل را خواهیم دید.

useCallback() برای جلوگیری از ارائه مجدد بیشتر

مسئله اصلی که باعث شد کودک دوباره رندر شود، بازآفرینی تابع handler است که مرجع ارسال شده به کودک را تغییر داد. بنابراین، ما باید راهی برای جلوگیری از این تفریح ​​داشته باشیم. اگر کنترل کننده دوباره ایجاد نشود، ارجاع به کنترل کننده تغییر نمی کند – بنابراین کودک دوباره ارائه نمی شود.

برای جلوگیری از ایجاد مجدد تابع هر بار که مولفه والد ارائه می شود، از یک قلاب React به نام استفاده می کنیم. useCallback(). هوک ها در React 16 معرفی شدند. برای کسب اطلاعات بیشتر در مورد هوک ها، می توانید به رسمی React نگاهی بیندازید. مستندات قلاب، یا “React Hooks: How to Get Started and Build Yoursel” را بررسی کنید.

این useCallback() hook دو آرگومان می گیرد: تابع callback و لیستی از وابستگی ها.

مثال زیر را در نظر بگیرید useCallback():

const handleClick = useCallback(() => {
  
}, [x,y]);

اینجا، useCallback() به اضافه می شود handleClick() عملکرد. استدلال دوم [x,y] می تواند یک آرایه خالی، یک وابستگی واحد یا لیستی از وابستگی ها باشد. هر زمان که هر وابستگی ذکر شده در آرگومان دوم تغییر کند، تنها در این صورت است handleClick() عملکرد دوباره ایجاد شود

اگر وابستگی های ذکر شده در useCallback() تغییر ندهید، یک نسخه حفظ شده از callback که به عنوان اولین آرگومان ذکر شده است، برگردانده می شود. ما مؤلفه عملکردی والد خود را برای استفاده از آن تغییر می دهیم useCallback() قلاب برای کنترل کننده که به مؤلفه فرزند منتقل می شود:


export default function Parent() {
  const [count, setCount] = useState(0);
  const handleClick = () => {
    setCount(count + 1);
  };

  const handler = useCallback(() => { 
    console.log("handler");
  }, []);

  console.log("Parent render");
  return (
    <div className="App">
      <button onClick={handleClick}>Increment</button>
      <h2>{count}</h2>
      <Child name={"joe"} childFunc={handler} />
    </div>
  );
}

کد مؤلفه فرزند همان طور که هست باقی می ماند.

کد کامل این مثال در زیر نشان داده شده است:


هنگامی که تعداد را در مؤلفه والد برای کد بالا افزایش می دهیم، می توانیم خروجی زیر را ببینیم:

Parent render
Child render
Parent render
Parent render
Parent render

از آنجایی که ما از useCallback() قلاب برای کنترل کننده والد، هر بار که والد دوباره ارائه می دهد، عملکرد کنترل کننده دوباره ایجاد نمی شود و یک نسخه ذخیره شده از کنترل کننده برای کودک ارسال می شود. مؤلفه فرزند یک مقایسه سطحی انجام می دهد و متوجه می شود که مرجع تابع کنترل کننده تغییر نکرده است – بنابراین آن را فراخوانی نمی کند. render روش.

چیز هایی برای به یاد آوردن

یادداشت کردن یک تکنیک خوب برای بهبود عملکرد در برنامه‌های React با پرهیز از ارائه مجدد غیرضروری یک مؤلفه در صورتی که ویژگی‌ها یا وضعیت آن تغییر نکرده باشد، است. ممکن است به فکر اضافه کردن حافظه برای همه مؤلفه ها باشید، اما این راه خوبی برای ساخت مؤلفه های React شما نیست. فقط در مواردی که جزء:

  • هنگامی که به همان props داده می شود همان خروجی را برمی گرداند
  • دارای چندین عنصر UI است و بررسی DOM مجازی بر عملکرد تأثیر می گذارد
  • اغلب همان لوازم را ارائه می کند

نتیجه

در این آموزش دیدیم:

  • چگونه React UI را رندر می کند
  • چرا حفظ کردن مورد نیاز است
  • نحوه پیاده سازی حافظه در React through React.memo() برای یک کامپوننت عملکردی React و React.PureComponent برای یک جزء کلاس
  • یک مورد استفاده که در آن، حتی پس از استفاده React.memo()، مؤلفه فرزند دوباره ارائه می شود
  • نحوه استفاده از useCallback() برای جلوگیری از رندر مجدد زمانی که یک تابع به عنوان پایه به مولفه فرزند ارسال می شود.

امیدوارم این مقدمه برای React memoization برای شما مفید بوده باشد!