بهبود سرعت برنامه همبرگر ساز و نکات تکمیلی (پایان فصل 5)

22 بهمن 1399
بهبود سرعت برنامه همبرگر ساز و نکات تکمیلی (پایان فصل 5)

بهبود سرعت برنامه در ری اکت و نکات تکمیلی

پروژه ی ما در این فصل به پایان رسیده است اما هنوز برخی از سوالات باقی مانده است؛ آیا نباید از PureComponent ها یا shouldComponentUpdate استفاده کنیم؟ اگر بخواهیم از این دو مورد استفاده کنیم، چه کاری باید انجام دهیم؟

برای استفاده از این Lifecycle باید از خودمان بپرسیم آیا در قسمتی از برنامه تغییرات state یا props ای را داریم که باعث render شدن دوباره برنامه شود با اینکه به چنین re-render ای نیاز نداریم؟ اگر به برنامه نگاهی بیندازیم متوجه می شویم که اکثر کلیک های ما UI را تغییر می دهند بنابراین باید باعث re-render شوند و از این نظر مشکلی نداریم. از طرفی استفاده ی بی رویه از Pure Component ها نیز باعث کاهش سرعت برنامه در ری اکت و سنگین تر شدن آن می شود. بگذارید با هم برنامه را چک کنیم؛ از قسمت های مهمی که باید چک بشوند BurgerBuilder است چرا که درون آن State اصلی برنامه را قرار داده ایم:

    state = {
        ingredients: {
            salad: 0,
            bacon: 0,
            cheese: 0,
            meat: 0
        },
        totalPrice: 4,
        purchasable: false,
        purchasing: false
    }

اما اگر به این موارد نگاه کنید (مخلفات و محتویات همبرگر، قیمت کلی، قابل سفارش بودن و ...) متوجه می شوید که با تغییر هر کدام، UI (شکل برنامه) نیز تغییر می کند. بنابراین باید آن ها را re-render کنیم تا تغییرات برای کاربر قابل نمایش باشد در غیر این صورت کاربر تغییری را مشاهده نخواهد کرد. اما اگر در همین فایل کمی بیشتر دقت کنیم عنصری را پیدا می کنیم که نیازی نیست با هر بار تغییر state دوباره render شود:

  <Modal show={this.state.purchasing} modalClosed={this.purchaseCancelHandler}>
     <OrderSummary
         ingredients={this.state.ingredients}
         price={this.state.totalPrice}
         purchaseCancelled={this.purchaseCancelHandler}
         purchaseContinued={this.purchaseContinueHandler}
     />
   </Modal>

بله منظور من عنصر <Modal> است که OrderSummary را دربرمی گیرد. اگر دقت کنید با تغییر محتوای همبرگر (state) عنصر OrderSummary نیز re-render می شود اما زمانی که Modal نمایش داده نمی شود چرا باید OrderSummary را re-render کنیم؟

به OrderSummary.js میرویم تا با استفاده از Lifecycle ها این موضوع را مدیریت کنیم. همانطور که گفتیم Lifecycle ها در کامپوننت های کاربردی در دسترس نیستند. بنابراین OrderSummary.js را به یک کامپوننت کلاس-محور تبدیل کنید:

import React, { Component } from 'react';
import Aux from '../../../hoc/Auxx';
import Button from '../../UI/Button/Button';

class OrderSummary extends Component {

    render() {

        const ingredientSummary = Object.keys(this.props.ingredients).map(igKey => {
            return (
                <li key={igKey}>
                    <span style={{ textTransform: 'capitalize' }}>{igKey}</span>: {this.props.ingredients[igKey]}
                </li>);
        });

        return (
            <Aux>
                <h3>Your Order</h3>
                <p>A delecious burger with the following ingredients:</p>
                <ul>
                    {ingredientSummary}
                </ul>
                <p><strong>Total Price: {this.props.price.toFixed(2)}</strong></p>
                <p>Continue to Checkout?</p>
                <Button btnType="Danger" clicked={this.props.purchaseCancelled}>CANCEL</Button>
                <Button btnType="Success" clicked={this.props.purchaseContinued}>CONTINUE</Button>
            </Aux>
        );
    }
}

export default OrderSummary;

حالا می توانیم درون آن از componentWillUpdate استفاده کنیم تا بفهمیم OrderSummary در چه موقعیت هایی re-render می شود:

class OrderSummary extends Component {

    componentWillUpdate() {
        console.log('[OrderSummary] WillUpdate');
    }

    render() {
// بقیه ی کد ها //

حالا اگر به مرورگر برویم و محتویات همبرگر را کم و زیاد کنیم در قسمت console چنین چیزی را مشاهده خواهیم کرد:

re-render شدن OrderSummary - پیام render شدن را در console میبینیم
re-render شدن OrderSummary - پیام render شدن را در console میبینیم

یعنی با هر بار کم و زیاد کردن محتویات همبرگر، OrderSummary نیز re-render می شود در حالی که اصلا کاربر OrderSummary را مشاهده نخواهد کرد! برای حل این مشکل به فایل Modal.js بروید و آن را هم به یک کامپوننت کلاس محور تبدیل کنید تا بتوانیم از Lifecycle ها در آن استفاده کنیم:

import React, { Component } from 'react';
import classes from './Modal.module.css';
import Aux from '../../../hoc/Auxx';
import Backdrop from '../Backdrop/Backdrop';

class Modal extends Component {

    render() {
        return (
            <Aux>
                <Backdrop show={this.props.show} clicked={this.props.modalClosed} />
                <div className={classes.Modal} style={{
                    transform: this.props.show ? 'translateY(0)' : 'translateY(-100vh)',
                    opacity: this.props.show ? '1' : '0'
                }}>
                    {this.props.children}
                </div>
            </Aux>
        );
    }
}

export default Modal;

یادتان باشد که می توانید به جای shouldComponentUpdate از react memo هم استفاده کنید که نتیجه ی یکسانی خواهد داشت. حالا باید shouldComponentUpdate را به این کلاس اضافه کنیم. می خواهیم بگوییم فقط زمانی re-render شود که مقدار show (در this.props.show) تغییر کند، یعنی کاربر روی ثبت سفارش کلیک کرده باشد. بنابراین:

    shouldComponentUpdate(nextProps, nextState) {
        return nextProps.show !== this.props.show;
    }

یعنی اگر show تغییر کرده باشد، نتیجه true خواهد بود که return شده و باعث re-render عنصر می شود. حالا componentWillUpdate را هم اضافه می کنیم:

    componentWillUpdate() {
        console.log('[Modal] WillUpdate');
    }

حالا اگر مرورگر را باز کنیم با اضافه کردن محتویات همبرگر هیچ چیزی در console نشان داده نمی شود اما به محض کلیک روی دکمه ی ORDER NOW عناصر مورد نظرمان re-render خواهند شد. به همین راحتی برنامه ی خود را بهینه سازی کرده ایم!

نکته ی مهمی در کاری که انجام دادیم وجود داشت: عنصر پدر re-render شدن عنصر فرزند را کنترل می کند! OrderSummary در کد های ما render نمی شد چرا که جلوی render شدن دوباره ی Modal (عنصر پدر) را گرفتیم.

ممکن است از خودتان بپرسید که چرا از lifecycle های دیگر مانند componentDidMount یا componentDidUpdate استفاده نکرده ایم؟ در فصل های بعدی که قابلیت های بیشتری مانند درخواست های HTTP را به برنامه مان اضافه کنیم از این lifecycle ها استفاده خواهیم کرد اما فعلا نیازی به آن ها نداریم. در واقع قبلا توضیح داده بودیم که componentDidUpdate و componentDidMount برای ایجاد side-effect ها کاربردی هستند (یعنی چیزهای شبیه به ایجاد درخواست های HTTP و دریافت داده از وب سرور) بنابراین هنوز نیازی به آن ها نداریم. lifecycle های دیگر نیز به ندرت استفاده می شوند و استفاده از آن ها موارد و شرایط خاصی را می طلبد.

در آخر بهتر است ساختار پوشه ها را کمی تغییر دهیم. اگر دقت کرده باشید کامپوننت Layout.js یک نوع HOC به حساب می آید چرا که فقط کامپوننت های دیگر را در بر می گیرد. بنابراین پوشه ی Layout را به درون پوشه ی hoc منتقل کنید. سپس درون پوشه ی hoc یک پوشه ی دیگر به نام Auxx میسازیم تا فایل Auxx.js را درون آن قرار دهیم. حالا که ساختار پوشه ها را دستکاری کرده ایم دستورات import ما بهم خواهد ریخت، بیایید سریعا آن ها را تصحیح کنیم:

وارد فایل Layout.js شده و import ها را به شکل زیر تغییر دهید:

import Aux from '../Auxx/Auxx';
import classes from './Layout.module.css';
import Toolbar from '../../components/Navigation/Toolbar/Toolbar';
import SideDrawer from '../../components/Navigation/SideDrawer/SideDrawer';

سپس وارد فایل App.js شده و دستور import مربوط به Layout را به شکل زیر تغییر دهید:

import Layout from './hoc/Layout/Layout';

اکنون Auxx را به صورت صحیح در فایل BurgerBuilder.js وارد می کنیم:

import Aux from '../../hoc/Auxx/Auxx';

همین کار را برای SideDrawer.js و Modal.js و OrderSummary نیز انجام می دهیم:

import Aux from '../../../hoc/Auxx/Auxx';

به شما تبریک میگویم شما یک اپلیکیشن خوب react ای ساختید! در فصل بعد راجع به درخواست های HTTP و AJAX صحبت خواهیم کرد.

دانلود کدهای کامل پروژه ی همبرگرساز (نسخه ی فصل 5)

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری دوره جامع آموزش ری اکت توصیه می‌کند:
نویسنده شوید
دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.