021-88881776

آموزش‌های پیشرفته (Advanced Guides) React

آموزش React از مراحل ابتدایی تا پیشرفته می‌تواند به توسعه‌دهندگان کمک کند تا به درک عمیق‌تری از قابلیت‌ها و امکانات پیشرفته این کتابخانه محبوب جاوااسکریپت برسند. در این مقاله، به بررسی جنبه‌های پیشرفته در React پرداخته می‌شود که با عنوان “آموزش‌های پیشرفته (Advanced Guides) React” شناخته می‌شود. این آموزش‌ها با ارائه نکات کاربردی و بهینه‌سازی‌ها، امکان پیاده‌سازی پروژه‌های بزرگ و پیچیده را فراهم می‌کنند.

Context API

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

مثال کاربردی

ساخت Context: ابتدا با استفاده از متد createContext یک context ایجاد می‌شود. به عنوان مثال، برای ایجاد یک context برای داده‌های مربوط به کاربر می‌توان به صورت زیر عمل کرد:

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

// ایجاد context برای کاربر
export const UserContext = createContext(null);

ایجاد یک Provider: Provider بخشی از Context است که داده‌ها را برای سایر کامپوننت‌ها فراهم می‌کند. در اینجا می‌توانیم کامپوننتی به عنوان UserProvider تعریف کنیم که وضعیت کاربر را مدیریت کند و آن را به عنوان value به Provider ارسال کند.

export const UserProvider = ({ children }) => {
    const [user, setUser] = useState({ name: 'Ali', age: 25 });

    return (
        <UserContext.Provider value={{ user, setUser }}>
            {children}
        </UserContext.Provider>
    );
};

استفاده از Context در کامپوننت‌های فرزند: اکنون می‌توانیم از این context در هر کامپوننتی که به اطلاعات کاربر نیاز دارد استفاده کنیم. برای دسترسی به context، از useContext استفاده می‌کنیم:

import React, { useContext } from 'react';
import { UserContext } from './UserProvider';

const UserProfile = () => {
    const { user, setUser } = useContext(UserContext);

    return (
        <div>
            <h2>نام کاربر: {user.name}</h2>
            <p>سن کاربر: {user.age}</p>
        </div>
    );
};

export default UserProfile;

Context API ابزاری قدرتمند برای مدیریت و به اشتراک‌گذاری داده‌ها در React است که نیاز به استفاده از پروپ‌ها برای انتقال داده‌ها بین کامپوننت‌ها را به حداقل می‌رساند. این API به خصوص برای برنامه‌های بزرگ و پیچیده که شامل لایه‌های مختلف کامپوننت‌ها هستند، مفید است و کدنویسی را ساده‌تر و قابل نگهداری‌تر می‌کند.

Error Boundaries (مرزهای خطا)

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

در React، مرزهای خطا تنها با استفاده از کامپوننت‌های کلاسی امکان‌پذیر است. یک کامپوننت مرز خطا می‌تواند در صورت بروز خطا، یک رابط کاربری جایگزین یا پیام خطا نمایش دهد، یا حتی خطاها را گزارش دهد، بدون اینکه دیگر بخش‌های برنامه متأثر شوند.

روش پیاده‌سازی مرزهای خطا

ساخت یک کامپوننت کلاسی برای مرز خطا: برای تعریف یک مرز خطا، باید یک کامپوننت کلاسی با متد componentDidCatch و getDerivedStateFromError ایجاد کنیم. getDerivedStateFromError باعث می‌شود کامپوننت پس از خطا به حالت خاصی تغییر وضعیت دهد و componentDidCatch وظیفه ثبت و مدیریت خطا را برعهده دارد.

import React, { Component } from 'react';

class ErrorBoundary extends Component {
    constructor(props) {
        super(props);
        this.state = { hasError: false };
    }

    static getDerivedStateFromError(error) {
        // به روز رسانی وضعیت به منظور نمایش UI جایگزین
        return { hasError: true };
    }

    componentDidCatch(error, errorInfo) {
        // ارسال اطلاعات خطا به یک سرویس گزارش خطا
        console.log("Error:", error, errorInfo);
    }

    render() {
        if (this.state.hasError) {
            // UI جایگزین هنگام بروز خطا
            return <h2>خطایی رخ داده است.</h2>;
        }

        return this.props.children;
    }
}

استفاده از Error Boundary در اطراف کامپوننت‌ها: اکنون می‌توانید این کامپوننت را مانند یک پوشش یا “مرز” برای بخش‌هایی از برنامه که ممکن است خطا رخ دهد استفاده کنید. به این ترتیب، اگر خطایی در کامپوننت یا کامپوننت‌های فرزند آن رخ دهد، پیام خطای مشخص شده به جای کل برنامه نمایش داده می‌شود.

import ErrorBoundary from './ErrorBoundary';
import UserProfile from './UserProfile';

function App() {
    return (
        <div>
            <ErrorBoundary>
                <UserProfile />
            </ErrorBoundary>
        </div>
    );
}

export default App;

نمونه خطا در کامپوننت‌های فرزند: هر کامپوننتی که درون مرز خطا قرار گیرد، اگر خطایی رخ دهد، مرز خطا آن را شناسایی کرده و به جای نمایش خطاهای سیستمی، پیام خطای مشخص شده را نمایش می‌دهد.

import React from 'react';

function UserProfile() {
    throw new Error('مشکلی در نمایش اطلاعات کاربر وجود دارد!'); // شبیه‌سازی خطا
    return <h2>نام کاربر: علی</h2>;
}

export default UserProfile;

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

فرگمنت‌ها (Fragments)

در React، فرگمنت‌ها یا Fragments ابزاری هستند که امکان گروه‌بندی چندین عنصر یا کامپوننت را بدون افزودن یک عنصر اضافی در DOM فراهم می‌کنند. به طور معمول، وقتی بخواهیم چند عنصر را در یک کامپوننت بدون داشتن یک عنصر والد مشترک بازگردانیم، نیاز است که آن‌ها را داخل یک عنصر مثل <div> بپیچیم. اما این کار باعث می‌شود که یک گره اضافی در DOM ایجاد شود که می‌تواند بر کارایی و ساختار کد تأثیر منفی بگذارد.

با استفاده از فرگمنت‌ها، می‌توان چندین عنصر را به عنوان فرزندهای مستقیم یک کامپوننت برگرداند، بدون اینکه این عناصر نیاز به یک والد اضافی داشته باشند. این ویژگی به بهینه‌سازی و ساده‌سازی ساختار DOM کمک می‌کند و دست توسعه‌دهندگان را برای طراحی‌های پیچیده‌تر و انعطاف‌پذیرتر باز می‌گذارد.

روش استفاده از فرگمنت‌ها در React

استفاده از React.Fragment: در این روش، از React.Fragment برای بسته‌بندی عناصر بدون افزودن گره DOM استفاده می‌شود. این متد به راحتی چندین عنصر را در خود گروه‌بندی کرده و خروجی را به صورت یکپارچه ارائه می‌دهد.

import React from 'react';

function ItemList() {
    return (
        <React.Fragment>
            <h2>لیست محصولات</h2>
            <ul>
                <li>محصول ۱</li>
                <li>محصول ۲</li>
                <li>محصول ۳</li>
            </ul>
        </React.Fragment>
    );
}

export default ItemList;

استفاده از سینتکس کوتاه <></>: یک روش ساده‌تر و مرسوم‌تر برای استفاده از فرگمنت‌ها، استفاده از سینتکس کوتاه <></> است که نیاز به تایپ کردن React.Fragment را حذف می‌کند و کد را مختصرتر می‌نماید. این روش نیز مانند مورد قبلی کار می‌کند و هیچ گره اضافی در DOM ایجاد نمی‌کند.

import React from 'react';

function ItemList() {
    return (
        <>
            <h2>لیست محصولات</h2>
            <ul>
                <li>محصول ۱</li>
                <li>محصول ۲</li>
                <li>محصول ۳</li>
            </ul>
        </>
    );
}

export default ItemList;

 

نکات اضافی در مورد فرگمنت‌ها

فرگمنت‌ها در مقایسه با استفاده از تگ‌های اضافی مانند <div> به بهبود عملکرد کمک می‌کنند، زیرا گره اضافی در DOM ساخته نمی‌شود و سبک‌دهی و جاوااسکریپت مستقیماً بر عناصر اصلی اعمال می‌شوند.
فرگمنت‌ها می‌توانند ویژگی‌هایی مانند key داشته باشند که به طور خاص در زمان استفاده از لیست‌ها برای بهبود عملکرد رندرینگ در React مفید است.
مثال با key در فرگمنت‌ها: در صورتی که نیاز به یک کلید (key) برای هر عنصر فرزند داشته باشیم (مثلاً در زمان تولید لیست‌ها)، می‌توانیم key را مستقیماً به فرگمنت اضافه کنیم.

import React from 'react';

function ItemList({ items }) {
    return (
        <>
            {items.map(item => (
                <React.Fragment key={item.id}>
                    <h2>{item.name}</h2>
                    <p>{item.description}</p>
                </React.Fragment>
            ))}
        </>
    );
}

export default ItemList;

فرگمنت‌ها در React یک ابزار کارآمد برای مدیریت ساختار DOM هستند و به توسعه‌دهندگان اجازه می‌دهند عناصر را بدون افزودن گره‌های اضافی به صورت منظم گروه‌بندی کنند. استفاده از React.Fragment و سینتکس کوتاه <></> به سادگی و بهینه‌سازی کد کمک می‌کند و منجر به ساختار بهینه‌تری برای برنامه‌های React می‌شود.

پورتال‌ها (Portals)

پورتال‌ها (Portals) در React به توسعه‌دهندگان این امکان را می‌دهند که محتوای یک کامپوننت را در جای دیگری از درخت DOM، خارج از سلسله‌مراتب اصلی کامپوننت‌ها، رندر کنند. به عبارت دیگر، با استفاده از پورتال‌ها، یک کامپوننت می‌تواند به گونه‌ای رندر شود که ساختار و چیدمان اصلی برنامه را حفظ کرده، اما از نظر نمایش در نقطه‌ای کاملاً متفاوت در صفحه قرار گیرد. این ویژگی در مواقعی که نیاز داریم محتوایی مانند مدال‌ها (Modals)، پنجره‌های پاپ‌آپ (Pop-ups)، یا ابزارک‌هایی که روی کل صفحه ظاهر می‌شوند، نمایش دهیم، بسیار مفید است؛ زیرا این عناصر باید در سطح بالای DOM قرار گیرند تا به راحتی از پس‌زمینه جدا و قابل دسترسی باشند.

روش استفاده از پورتال‌ها در React

ایجاد یک عنصر DOM برای پورتال: در مرحله اول، نیاز به یک عنصر HTML داریم که به عنوان نقطه هدف برای پورتال عمل کند. این عنصر معمولاً در فایل HTML اصلی (مثل index.html) قرار می‌گیرد و می‌تواند یک div ساده با یک شناسه خاص باشد:

<!-- index.html -->
<div id="modal-root"></div>

استفاده از ReactDOM.createPortal: در React، متد ReactDOM.createPortal برای رندر کردن محتوا در خارج از سلسله‌مراتب کامپوننت‌ها استفاده می‌شود. این متد دو آرگومان می‌گیرد: محتوای کامپوننتی که باید رندر شود و نقطه هدف آن در DOM.

در این مثال، یک کامپوننت مدال ساده با استفاده از پورتال ساخته می‌شود:

import React from 'react';
import ReactDOM from 'react-dom';

const Modal = ({ isOpen, children, onClose }) => {
    if (!isOpen) return null;

    return ReactDOM.createPortal(
        <div className="modal-overlay">
            <div className="modal-content">
                <button onClick={onClose}>بستن</button>
                {children}
            </div>
        </div>,
        document.getElementById('modal-root') // اشاره به عنصر DOM مدال
    );
};

export default Modal;

در اینجا، ReactDOM.createPortal کامپوننت مدال را داخل #modal-root در فایل HTML رندر می‌کند، حتی اگر این کامپوننت از نظر سلسله‌مراتب داخلی در جای دیگری قرار داشته باشد.

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

import React, { useState } from 'react';
import Modal from './Modal';

function App() {
    const [isModalOpen, setIsModalOpen] = useState(false);

    return (
        <div>
            <h1>سلام به برنامه React</h1>
            <button onClick={() => setIsModalOpen(true)}>باز کردن مدال</button>
            <Modal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)}>
                <h2>این یک مدال است!</h2>
                <p>این محتوای درون مدال است.</p>
            </Modal>
        </div>
    );
}

export default App;

مزایای استفاده از پورتال‌ها

جداسازی منطقی محتوا: مدال‌ها و پنجره‌های پاپ‌آپ، حتی اگر در درخت کامپوننت‌های React در جای دیگری تعریف شده باشند، می‌توانند خارج از ساختار فعلی در DOM اصلی رندر شوند.
بهبود دسترسی: استفاده از پورتال‌ها به مدیریت بهتر لایه‌بندی و جلوگیری از مشکلات ز-index کمک می‌کند و باعث می‌شود این عناصر همیشه در بالای دیگر محتوا نمایش داده شوند.
پورتال‌ها در React ابزاری مفید برای رندر کردن محتوای کامپوننت‌ها در خارج از سلسله‌مراتب DOM اصلی هستند. این ویژگی به ویژه برای ساخت عناصر تعاملی مانند مدال‌ها، پاپ‌آپ‌ها، و تولتیپ‌ها بسیار مفید است، زیرا این عناصر باید از نظر بصری و ساختاری از باقی برنامه جدا باشند تا تجربه کاربری بهتری ایجاد کنند.

Render Props (رندر پراپ‌ها)

الگوی Render Props در React یکی از الگوهای رایج برای به اشتراک‌گذاری رفتار بین کامپوننت‌ها است. این الگو به شما اجازه می‌دهد که یک کامپوننت را طوری طراحی کنید که عملکرد یا داده‌های خاصی را به فرزند خود بدهد و از این طریق، رفتارهای مشترک را میان کامپوننت‌های مختلف به اشتراک بگذارید. به جای آنکه یک کامپوننت صرفاً UI خاصی را بازگرداند، می‌توان با استفاده از یک پراپ به نام render یا children یک تابع به آن پاس داد که تعیین کند چگونه کامپوننت فرزند، داده یا رفتار مورد نظر را نمایش دهد.

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

روش استفاده از Render Props در React

ساخت کامپوننت با Render Props

فرض کنیم می‌خواهیم یک کامپوننت بسازیم که حرکت نشانگر ماوس در صفحه را رهگیری کند. با استفاده از Render Props می‌توانیم رفتار رهگیری مکان ماوس را ایجاد کرده و سپس به فرزندان اجازه دهیم که نحوه استفاده از این داده‌ها را خودشان تعیین کنند.

import React, { Component } from 'react';

class MouseTracker extends Component {
    constructor(props) {
        super(props);
        this.state = { x: 0, y: 0 };
    }

    handleMouseMove = (event) => {
        this.setState({
            x: event.clientX,
            y: event.clientY
        });
    };

    render() {
        return (
            <div onMouseMove={this.handleMouseMove} style={{ height: '100vh' }}>
                {this.props.render(this.state)}
            </div>
        );
    }
}

export default MouseTracker;

در این مثال، کامپوننت MouseTracker رفتار رهگیری مکان ماوس را فراهم کرده و به جای اینکه به طور مستقیم داده‌ها را نمایش دهد، این داده‌ها را به تابع render که به عنوان پراپ دریافت می‌کند، می‌سپارد. این تابع سپس می‌تواند تصمیم بگیرد که داده‌ها چگونه نمایش داده شوند.

استفاده از کامپوننت با Render Props:

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

import React from 'react';
import MouseTracker from './MouseTracker';

function App() {
    return (
        <div>
            <h1>رندر پراپ‌ها در React</h1>
            <MouseTracker render={({ x, y }) => (
                <h2>مکان ماوس: ({x}, {y})</h2>
            )} />
        </div>
    );
}

export default App;

استفاده از پراپ children به عنوان تابع Render Props

همچنین می‌توانیم از پراپ children به عنوان یک تابع Render Props استفاده کنیم، که روش رایج‌تری برای پیاده‌سازی الگوی Render Props است.

function App() {
    return (
        <MouseTracker>
            {({ x, y }) => (
                <h2>مکان ماوس: ({x}, {y})</h2>
            )}
        </MouseTracker>
    );
}

مزایای استفاده از Render Props

کاهش تکرار کد: با استفاده از این الگو، می‌توانیم کد مشترک را به صورت ماژولار در یک کامپوننت قرار دهیم و سپس آن را به راحتی در چندین نقطه استفاده کنیم.
افزایش انعطاف‌پذیری: این الگو به ما امکان می‌دهد که رفتارها را جدا از نحوه نمایش پیاده‌سازی کنیم و در نتیجه به راحتی بتوانیم از یک رفتار در چندین کامپوننت با نحوه نمایش‌های مختلف استفاده کنیم.
الگوی Render Props در React راهی مناسب برای به اشتراک‌گذاری رفتارها و داده‌ها بین کامپوننت‌ها است و باعث افزایش انعطاف‌پذیری و کاهش پیچیدگی در کدنویسی می‌شود. با استفاده از این الگو، می‌توان کامپوننت‌های قابل استفاده مجدد و ساختارمندتری را ایجاد کرد که به راحتی در بخش‌های مختلف برنامه به کار روند.

کامپوننت‌های مرتبه بالا (Higher-Order Components)

کامپوننت‌های مرتبه بالا یا Higher-Order Components (HOCs) در React یکی از الگوهای پرکاربرد هستند که برای افزودن رفتارها یا ویژگی‌های خاص به کامپوننت‌های موجود مورد استفاده قرار می‌گیرند. HOC در واقع یک تابع است که یک کامپوننت را به عنوان ورودی دریافت می‌کند و یک کامپوننت جدید را برمی‌گرداند. این کامپوننت جدید قابلیت‌های اضافه‌ای را به کامپوننت ورودی می‌دهد، بدون آنکه ساختار یا کد اصلی آن را تغییر دهد.

HOCها معمولاً برای افزایش قابلیت استفاده مجدد (reusability) در برنامه‌های بزرگ React استفاده می‌شوند و به شما امکان می‌دهند که کدهای مشابه را بدون تکرار در چندین کامپوننت به کار ببرید. این الگو به ویژه زمانی مفید است که نیاز دارید برخی از رفتارهای مشترک مانند مدیریت داده‌ها، دسترسی به وضعیت، و کار با توابع جانبی (مثل احراز هویت یا مجوز) را به چندین کامپوننت اضافه کنید.

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

روش ایجاد کامپوننت‌های مرتبه بالا در React

ایجاد یک HOC ساده برای افزودن رفتار خاص:
فرض کنیم می‌خواهیم یک HOC بسازیم که داده‌ها را از یک منبع خاص (مثلاً API) دریافت کرده و به کامپوننت اصلی اضافه کند.

import React, { Component } from 'react';

function withDataFetching(WrappedComponent, dataSource) {
    return class extends Component {
        constructor(props) {
            super(props);
            this.state = {
                data: [],
                isLoading: true,
                error: null
            };
        }

        componentDidMount() {
            fetch(dataSource)
                .then(response => response.json())
                .then(data => this.setState({ data, isLoading: false }))
                .catch(error => this.setState({ error, isLoading: false }));
        }

        render() {
            const { data, isLoading, error } = this.state;

            return (
                <WrappedComponent
                    data={data}
                    isLoading={isLoading}
                    error={error}
                    {...this.props}
                />
            );
        }
    };
}

export default withDataFetching;

در اینجا، HOC به نام withDataFetching ایجاد کرده‌ایم که یک کامپوننت را به عنوان ورودی دریافت می‌کند و داده‌ها را از منبع مشخص شده (dataSource) واکشی کرده و به عنوان پروپ‌های جدید به کامپوننت ورودی (WrappedComponent) ارسال می‌کند. در نتیجه، هر کامپوننتی که با استفاده از withDataFetching احاطه شود، به قابلیت دریافت داده از API و مدیریت وضعیت بارگیری دسترسی خواهد داشت.

استفاده از HOC در کامپوننت‌ها:
حالا فرض کنید یک کامپوننت به نام UserList داریم که لیست کاربران را نمایش می‌دهد. با استفاده از HOC withDataFetching می‌توانیم به راحتی قابلیت واکشی داده‌ها را به این کامپوننت اضافه کنیم.

import React from 'react';
import withDataFetching from './withDataFetching';

const UserList = ({ data, isLoading, error }) => {
    if (isLoading) return <p>در حال بارگذاری...</p>;
    if (error) return <p>خطا: {error.message}</p>;

    return (
        <ul>
            {data.map(user => (
                <li key={user.id}>{user.name}</li>
            ))}
        </ul>
    );
};

export default withDataFetching(UserList, 'https://jsonplaceholder.typicode.com/users');

در اینجا، UserList با استفاده از withDataFetching احاطه شده است و از این طریق، داده‌ها از API واکشی شده و به UserList ارسال می‌شوند. به این ترتیب، بدون تغییر در کد اصلی UserList، آن را به کامپوننتی مجهز به قابلیت بارگیری داده تبدیل کرده‌ایم.

افزودن چندین HOC به یک کامپوننت:
گاهی ممکن است بخواهید چندین رفتار مختلف را به یک کامپوننت اضافه کنید. برای این منظور می‌توانید از چندین HOC به صورت زنجیره‌ای استفاده کنید:

export default withDataFetching(withAnotherFeature(UserList));

مزایای استفاده از کامپوننت‌های مرتبه بالا (HOC)

کاهش تکرار کد: با استفاده از HOCها، می‌توان به راحتی قابلیت‌ها و رفتارهای مشترک را بدون تکرار کد به چندین کامپوننت اضافه کرد.
افزایش انعطاف‌پذیری: HOCها به شما اجازه می‌دهند که رفتارهای اضافی را به کامپوننت‌ها اضافه کنید، بدون آنکه نیاز به تغییر در ساختار یا منطق اصلی کامپوننت‌ها باشد.
افزایش قابلیت استفاده مجدد: HOCها رفتارهای قابل استفاده مجدد ایجاد می‌کنند که می‌توانند در سراسر برنامه استفاده شوند.
کامپوننت‌های مرتبه بالا (HOC) در React یک ابزار قوی برای به اشتراک‌گذاری رفتارها و قابلیت‌های مشترک بین کامپوننت‌ها هستند. این الگو به شما اجازه می‌دهد که با ساختاردهی کد و کاهش تکرار، پروژه‌های React خود را انعطاف‌پذیرتر و قابل نگهداری‌تر کنید.

React و سرور (React and Server)

تعامل React با سرور یکی از موضوعات مهم در توسعه برنامه‌های مدرن وب است، به ویژه در برنامه‌های بزرگ و پویا که نیاز به سرعت بارگذاری بالا و سئوی بهینه دارند. دو روش عمده برای تعامل React با سرور شامل رندرینگ سمت کلاینت (Client-Side Rendering) و رندرینگ سمت سرور (Server-Side Rendering یا SSR) است. هر کدام از این روش‌ها کاربردهای خاص خود را دارند و بسته به نیاز پروژه و هدف مورد نظر می‌توانند استفاده شوند.

Client-Side Rendering (CSR)

در رندرینگ سمت کلاینت، برنامه React ابتدا در مرورگر کاربر بارگذاری می‌شود، سپس داده‌ها از طریق درخواست‌های API به سرور فرستاده می‌شوند و پس از دریافت، صفحه به کاربر نمایش داده می‌شود. این روش برای بسیاری از برنامه‌های وب کافی است، اما ممکن است زمان بارگذاری اولیه برای کاربر کند باشد، به ویژه در برنامه‌های بزرگ. همچنین، این روش ممکن است برای بهینه‌سازی موتورهای جستجو (SEO) محدودیت‌هایی داشته باشد.

Server-Side Rendering (SSR)

در رندرینگ سمت سرور، سرور پیش از ارسال کد HTML به مرورگر، برنامه React را رندر می‌کند. در این روش، صفحه به صورت کامل با محتوای مورد نیاز بارگذاری می‌شود و سپس به کاربر ارسال می‌گردد. SSR به خصوص برای بهبود سئو و سرعت بارگذاری صفحات کاربرد دارد و باعث می‌شود که کاربران به سرعت محتوای اولیه را مشاهده کنند.

مزایای استفاده از SSR (Server-Side Rendering)

بهبود سئو (SEO): موتورهای جستجوگر معمولاً با صفحات HTML از پیش رندر شده بهتر کار می‌کنند. با استفاده از SSR، محتوا به صورت کامل در HTML بارگذاری شده و ربات‌های جستجوگر می‌توانند به راحتی محتوا را بخوانند و فهرست‌بندی کنند.
سرعت بارگذاری اولیه بیشتر: SSR به کاربران امکان می‌دهد تا صفحه بارگذاری شده را سریع‌تر مشاهده کنند، زیرا سرور ابتدا صفحه را رندر کرده و به صورت HTML به مرورگر ارسال می‌کند.
بهبود تجربه کاربری (UX): کاربران به سرعت محتوای اولیه را می‌بینند و منتظر بارگذاری داده‌ها نمی‌مانند، که می‌تواند تجربه کاربری را بهبود بخشد.

پیاده‌سازی Server-Side Rendering در React

استفاده از Next.js برای پیاده‌سازی SSR:
فریم‌ورک Next.js یکی از محبوب‌ترین ابزارها برای پیاده‌سازی SSR در برنامه‌های React است. با استفاده از Next.js، شما می‌توانید به راحتی برنامه‌های React را به صورت SSR و بدون نیاز به تنظیمات پیچیده پیاده‌سازی کنید. برای مثال، با استفاده از متد getServerSideProps در Next.js می‌توانید داده‌ها را قبل از رندر سرور دریافت کرده و به کامپوننت ارسال کنید.

// pages/index.js
import React from 'react';

function HomePage({ data }) {
    return (
        <div>
            <h1>صفحه اصلی با SSR</h1>
            <ul>
                {data.map(item => (
                    <li key={item.id}>{item.title}</li>
                ))}
            </ul>
        </div>
    );
}

export async function getServerSideProps() {
    const res = await fetch('https://jsonplaceholder.typicode.com/posts');
    const data = await res.json();

    return { props: { data } };
}

export default HomePage;

در این مثال، داده‌ها قبل از رندر سرور از یک API واکشی شده و به کامپوننت HomePage به عنوان props ارسال می‌شوند. این باعث می‌شود که صفحه به صورت کاملاً رندر شده به کاربر ارسال شود.

پیاده‌سازی SSR به صورت دستی با استفاده از Express و ReactDOMServer

اگر از فریم‌ورک خاصی مانند Next.js استفاده نمی‌کنید، می‌توانید به صورت دستی SSR را با استفاده از Express و ماژول ReactDOMServer پیاده‌سازی کنید.

import express from 'express';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import App from './App';

const server = express();

server.get('*', (req, res) => {
    const appHtml = ReactDOMServer.renderToString(<App />);
    const html = `
        <!DOCTYPE html>
        <html>
            <head>
                <title>React SSR</title>
            </head>
            <body>
                <div id="root">${appHtml}</div>
                <script src="/bundle.js"></script>
            </body>
        </html>
    `;
    res.send(html);
});

server.listen(3000, () => {
    console.log('Server is running on http://localhost:3000');
});

در این مثال، برنامه React با استفاده از ReactDOMServer.renderToString به صورت سرور رندر شده و سپس HTML نهایی به مرورگر ارسال می‌شود. به این ترتیب، کاربران قبل از اینکه JavaScript بارگذاری شود، می‌توانند صفحه را مشاهده کنند.استفاده از Server-Side Rendering در React به شما امکان می‌دهد تا برنامه‌هایی با بارگذاری سریع‌تر و بهبود سئو ایجاد کنید. فریم‌ورک‌هایی مانند Next.js این فرآیند را بسیار ساده می‌کنند، اما می‌توان SSR را به صورت دستی نیز با استفاده از Express و ReactDOMServer پیاده‌سازی کرد. در هر صورت، با SSR تجربه کاربری و عملکرد برنامه به طور قابل توجهی بهبود می‌یابد.

پروژه‌های با عملکرد بالا (Performance Optimization)

بهینه‌سازی عملکرد در React به معنای کاهش زمان بارگذاری، بهبود سرعت رندرینگ و افزایش واکنش‌پذیری برنامه است. در پروژه‌های بزرگ و پیچیده، بهینه‌سازی عملکرد اهمیت بسیار زیادی دارد، زیرا عملکرد ضعیف می‌تواند تجربه کاربری را به شدت تحت تأثیر قرار دهد. React ابزارها و تکنیک‌های مختلفی را برای بهینه‌سازی ارائه می‌دهد که شامل memoization، lazy-loading، dynamic import و تکنیک‌های دیگر برای بهبود بهره‌وری و کاهش مصرف منابع است.

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

تکنیک‌های کلیدی برای بهینه‌سازی عملکرد در React

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

import React from 'react';

const MyComponent = React.memo(({ name }) => {
    console.log('رندر شد');
    return <h2>سلام، {name}!</h2>;
});

export default MyComponent;

در این مثال، MyComponent تنها زمانی دوباره رندر می‌شود که پروپ name تغییر کند، و به این ترتیب از رندرهای غیرضروری جلوگیری می‌کند.

استفاده از useMemo و useCallback

useMemo یک هوک است که می‌تواند نتیجه محاسبات پیچیده را ذخیره (memoize) کند و فقط در صورت تغییر وابستگی‌ها محاسبه را دوباره انجام دهد.
useCallback نیز مشابه useMemo است، اما به جای ذخیره یک مقدار، یک تابع را ذخیره می‌کند.

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

function ExpensiveComponent({ items }) {
    const [count, setCount] = useState(0);

    const expensiveCalculation = useMemo(() => {
        console.log('محاسبه پیچیده انجام شد');
        return items.reduce((acc, item) => acc + item.value, 0);
    }, [items]);

    const increment = useCallback(() => setCount(count + 1), [count]);

    return (
        <div>
            <p>مجموع: {expensiveCalculation}</p>
            <button onClick={increment}>افزایش</button>
        </div>
    );
}

در اینجا، expensiveCalculation فقط زمانی که items تغییر کند محاسبه می‌شود و increment نیز با استفاده از useCallback ذخیره شده و فقط زمانی تغییر می‌کند که count تغییر کند.

Lazy Loading و Dynamic Imports: Lazy Loading به معنای بارگذاری بخش‌هایی از برنامه در زمان نیاز به آن‌ها است. React از React.lazy و Suspense برای بارگذاری تنبل کامپوننت‌ها استفاده می‌کند. این روش باعث می‌شود که تنها بخش‌های ضروری هنگام بارگذاری اولیه لود شوند و مابقی فقط در صورت نیاز بارگیری شوند.

import React, { Suspense } from 'react';

const LazyComponent = React.lazy(() => import('./LazyComponent'));

function App() {
    return (
        <div>
            <h1>برنامه من</h1>
            <Suspense fallback={<div>در حال بارگذاری...</div>}>
                <LazyComponent />
            </Suspense>
        </div>
    );
}

export default App;

با استفاده از React.lazy و Suspense، کامپوننت LazyComponent تنها زمانی که مورد نیاز باشد بارگیری می‌شود، که باعث کاهش حجم بارگیری اولیه می‌شود.

کاهش اندازه باندل با Dynamic Imports: با استفاده از importهای دینامیک، کد را می‌توان در باندل‌های مختلف تقسیم کرد که تنها زمانی بارگیری می‌شوند که کاربر واقعاً به آن‌ها نیاز داشته باشد. این تکنیک به کاهش اندازه فایل اصلی کمک می‌کند و به ویژه برای بهینه‌سازی زمان بارگذاری اولیه بسیار مؤثر است.

const handleButtonClick = async () => {
    const { default: SomeComponent } = await import('./SomeComponent');
    // اکنون می‌توانیم از SomeComponent استفاده کنیم
};

Virtualization برای رندر کردن لیست‌های بزرگ: زمانی که یک لیست بزرگ داریم، رندر کردن تمام آیتم‌ها می‌تواند منابع سیستم را به شدت مصرف کند. برای حل این مشکل، می‌توان از کتابخانه‌هایی مانند react-window یا react-virtualized استفاده کرد که فقط آیتم‌های قابل مشاهده در viewport را رندر می‌کنند.

import { FixedSizeList as List } from 'react-window';

const MyList = ({ items }) => (
    <List
        height={300}
        itemCount={items.length}
        itemSize={35}
        width="100%"
    >
        {({ index, style }) => (
            <div style={style}>
                {items[index].name}
            </div>
        )}
    </List>
);

استفاده بهینه از shouldComponentUpdate و PureComponent: در کامپوننت‌های کلاسی، می‌توان از متد shouldComponentUpdate برای جلوگیری از رندرهای غیرضروری استفاده کرد. همچنین، PureComponent یک کلاس در React است که به صورت خودکار بررسی می‌کند که آیا پروپ‌ها یا state تغییر کرده‌اند و فقط در صورت نیاز کامپوننت را دوباره رندر می‌کند. بهینه‌سازی عملکرد در React با استفاده از تکنیک‌هایی مانند React.memo، useMemo، lazy-loading، dynamic imports و virtualization کمک می‌کند که بارگذاری صفحه سریع‌تر انجام شود و رندرهای غیرضروری به حداقل برسند. این تکنیک‌ها به توسعه‌دهندگان امکان می‌دهند که با مدیریت بهتر منابع، تجربه کاربری بهتری ارائه دهند و از اتلاف منابع سیستم جلوگیری کنند.

مهاجرت به فانکشنال کامپوننت‌ها (Migrating to Functional Components)

با انتشار هوک‌ها در React نسخه 16.8، فانکشنال کامپوننت‌ها تبدیل به گزینه‌ای قدرتمند برای ساخت کامپوننت‌ها در React شدند. پیش از این، کامپوننت‌های کلاس تنها راهی بودند که امکان مدیریت وضعیت (state) و دسترسی به متدهای چرخه عمر (مثل componentDidMount, componentDidUpdate) را فراهم می‌کردند. اما با اضافه شدن هوک‌ها، فانکشنال کامپوننت‌ها هم می‌توانند از قابلیت‌های مشابه بهره‌مند شوند و به همین دلیل، بسیاری از پروژه‌های React به سمت استفاده از فانکشنال کامپوننت‌ها مهاجرت کرده‌اند.

مزایای فانکشنال کامپوننت‌ها و هوک‌ها

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

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

کاهش پیچیدگی در مدیریت وضعیت: استفاده از هوک‌ها مانند useState, useEffect, و useContext، امکان مدیریت وضعیت و چرخه عمر را بدون نیاز به کلاس فراهم می‌کند. همچنین، کامپوننت‌ها از نظر مقیاس‌پذیری و خوانایی بهتر عمل می‌کنند.

افزایش قابلیت استفاده مجدد و جداسازی منطق: با هوک‌های سفارشی (Custom Hooks)، می‌توان منطق‌های مشترک بین کامپوننت‌ها را به صورت جداگانه پیاده‌سازی کرد و آن‌ها را به راحتی بین چندین کامپوننت به اشتراک گذاشت.

نحوه تبدیل کامپوننت‌های کلاس به فانکشنال کامپوننت

مثال کامپوننت کلاس:
ابتدا، یک کامپوننت کلاس ساده داریم که وضعیت (state) و چرخه عمر را مدیریت می‌کند.

import React, { Component } from 'react';

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

    componentDidMount() {
        console.log('کامپوننت نصب شد');
    }

    componentDidUpdate() {
        console.log('کامپوننت به‌روزرسانی شد');
    }

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

    render() {
        return (
            <div>
                <p>شمارش: {this.state.count}</p>
                <button onClick={this.increment}>افزایش</button>
            </div>
        );
    }
}

export default Counter;

تبدیل به فانکشنال کامپوننت با استفاده از هوک‌ها

اکنون این کامپوننت را به فانکشنال کامپوننت تبدیل می‌کنیم و از هوک‌ها برای مدیریت وضعیت و چرخه عمر استفاده می‌کنیم.

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

function Counter() {
    const [count, setCount] = useState(0);

    useEffect(() => {
        console.log('کامپوننت نصب شد');
        return () => console.log('کامپوننت از بین رفت');
    }, []);

    useEffect(() => {
        console.log('کامپوننت به‌روزرسانی شد');
    }, [count]);

    const increment = () => setCount(count + 1);

    return (
        <div>
            <p>شمارش: {count}</p>
            <button onClick={increment}>افزایش</button>
        </div>
    );
}

export default Counter;

توضیحات تبدیل

استفاده از useState: به جای this.state و this.setState از useState استفاده می‌کنیم که یک آرایه برمی‌گرداند که شامل مقدار وضعیت و تابع تغییر دهنده وضعیت است.
استفاده از useEffect: به جای componentDidMount و componentDidUpdate از useEffect استفاده می‌کنیم. اولین useEffect بدون وابستگی خاصی فقط یک‌بار اجرا می‌شود و برای مدیریت چرخه عمر نصب (mount) کامپوننت استفاده می‌شود. دومین useEffect، وابسته به count است و هر بار که مقدار count تغییر کند، اجرا می‌شود.
هوک‌های سفارشی برای جداسازی منطق مشترک: می‌توان از هوک‌های سفارشی (Custom Hooks) برای جداسازی منطق مشترک در فانکشنال کامپوننت‌ها استفاده کرد. به عنوان مثال، اگر چندین کامپوننت نیاز به مدیریت شمارش داشتند، می‌توانستیم یک هوک سفارشی بسازیم و این منطق را به راحتی به اشتراک بگذاریم:

import { useState } from 'react';

function useCounter(initialCount = 0) {
    const [count, setCount] = useState(initialCount);
    const increment = () => setCount(count + 1);
    return [count, increment];
}

function Counter() {
    const [count, increment] = useCounter();

    return (
        <div>
            <p>شمارش: {count}</p>
            <button onClick={increment}>افزایش</button>
        </div>
    );
}

export default Counter;

در اینجا، useCounter به عنوان یک هوک سفارشی، منطق مدیریت شمارش را به کامپوننت‌های مختلف ارائه می‌دهد، بدون نیاز به تکرار کد.

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

شیوه‌های مدیریت داده (Data Flow and State Management)

در برنامه‌های React، به ویژه پروژه‌های بزرگ و پیچیده، مدیریت داده‌ها و وضعیت (state) یکی از چالش‌های اصلی است. با افزایش تعداد کامپوننت‌ها و پیچیدگی داده‌ها، نیاز به راهکارهای پیشرفته‌تر و ساختاریافته‌تر برای مدیریت وضعیت احساس می‌شود. مدیریت داده‌ها شامل انتقال صحیح و موثر وضعیت و داده‌ها بین کامپوننت‌ها، به اشتراک‌گذاری آن‌ها در بخش‌های مختلف برنامه و جلوگیری از به‌روزرسانی‌های غیرضروری است. برای مدیریت داده‌ها، از ابزارهایی مانند Context API و Redux استفاده می‌شود که هر کدام دارای ویژگی‌ها و کاربردهای مخصوص به خود هستند.

ابزارهای مدیریت وضعیت در React

Context API
Context API که به صورت داخلی در React تعبیه شده، ابزاری سبک و ساده برای به اشتراک‌گذاری وضعیت در بین کامپوننت‌ها است. Context به شما اجازه می‌دهد که داده‌ها را از طریق یک context provider به صورت مستقیم به تمام کامپوننت‌های فرزند انتقال دهید و نیاز به استفاده از پروپ‌ها (props) برای ارسال داده‌ها در سلسله‌مراتب را حذف کنید. با این حال، Context API معمولاً برای برنامه‌های کوچک یا بخش‌هایی از برنامه که نیاز به اشتراک‌گذاری داده‌های محدود دارند، مناسب است.

مثال استفاده از Context API: فرض کنید می‌خواهیم یک برنامه داشته باشیم که وضعیت ورود کاربر (login state) را مدیریت کند.

import React, { createContext, useContext, useState } from 'react';

// ساخت Context برای وضعیت کاربر
const AuthContext = createContext();

export const AuthProvider = ({ children }) => {
    const [isLoggedIn, setIsLoggedIn] = useState(false);

    const login = () => setIsLoggedIn(true);
    const logout = () => setIsLoggedIn(false);

    return (
        <AuthContext.Provider value={{ isLoggedIn, login, logout }}>
            {children}
        </AuthContext.Provider>
    );
};

export const useAuth = () => useContext(AuthContext);

اکنون، می‌توانیم در هر جای برنامه از وضعیت کاربر با استفاده از useAuth استفاده کنیم:

import React from 'react';
import { useAuth } from './AuthContext';

const Navbar = () => {
    const { isLoggedIn, login, logout } = useAuth();

    return (
        <nav>
            {isLoggedIn ? (
                <button onClick={logout}>خروج</button>
            ) : (
                <button onClick={login}>ورود</button>
            )}
        </nav>
    );
};

export default Navbar;

Redux
Redux یک کتابخانه قدرتمند و محبوب برای مدیریت وضعیت جهانی در برنامه‌های React است. Redux به ویژه در پروژه‌های بزرگ که نیاز به مدیریت وضعیت پیچیده و داده‌های مشترک در سرتاسر برنامه دارند، بسیار مفید است. Redux یک ساختار خاص برای مدیریت داده‌ها فراهم می‌کند و به شما اجازه می‌دهد که با استفاده از اکشن‌ها (actions) و ریدوسرها (reducers)، وضعیت را به طور مرکزی مدیریت کنید. همچنین Redux ابزارهایی برای دیباگ (debugging) و ابزارهای کمکی (middleware) برای مدیریت درخواست‌های غیرهمزمان ارائه می‌دهد.

ساختار Redux:

استور (Store): ذخیره مرکزی وضعیت برنامه.
اکشن‌ها (Actions): توابعی که تغییرات وضعیت را توصیف می‌کنند.
ریدوسرها (Reducers): تابع‌هایی که وضعیت جدید را بر اساس اکشن‌ها تعیین می‌کنند.
مثال پیاده‌سازی Redux: در این مثال، وضعیت مربوط به سبد خرید (cart) را مدیریت می‌کنیم.

تعریف اکشن‌ها:

// actions/cartActions.js
export const addToCart = (item) => ({
    type: 'ADD_TO_CART',
    payload: item,
});

export const removeFromCart = (itemId) => ({
    type: 'REMOVE_FROM_CART',
    payload: itemId,
});

تعریف ریدوسر:

// reducers/cartReducer.js
const initialState = {
    items: [],
};

const cartReducer = (state = initialState, action) => {
    switch (action.type) {
        case 'ADD_TO_CART':
            return { ...state, items: [...state.items, action.payload] };
        case 'REMOVE_FROM_CART':
            return {
                ...state,
                items: state.items.filter(item => item.id !== action.payload),
            };
        default:
            return state;
    }
};

export default cartReducer;

ایجاد Store:

import { createStore } from 'redux';
import cartReducer from './reducers/cartReducer';

const store = createStore(cartReducer);
export default store;

استفاده از Store در برنامه: برای استفاده از وضعیت مدیریت شده توسط Redux در برنامه، باید کامپوننت‌ها را با استفاده از Provider به Store متصل کنید.

import React from 'react';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';

const Root = () => (
    <Provider store={store}>
        <App />
    </Provider>
);

export default Root;

Context API و Redux هر دو ابزارهای قدرتمندی برای مدیریت وضعیت در React هستند. Context API برای موارد ساده و برنامه‌های کوچک مناسب است و استفاده از آن بسیار راحت است. از سوی دیگر، Redux برای پروژه‌های بزرگ و پیچیده که نیاز به مدیریت وضعیت پیچیده و اشتراک‌گذاری داده‌ها در سطح جهانی دارند، ایده‌آل است. با استفاده از این ابزارها، می‌توان پروژه‌هایی با ساختار بهتر و مدیریت داده بهینه‌تری ایجاد کرد و تجربه کاربری بهتری به کاربران ارائه داد.

چند رندر و بهینه‌سازی آن (Reconciliation and Performance)

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

الگوریتم reconciliation با مقایسه وضعیت فعلی و قبلی یک کامپوننت، تشخیص می‌دهد که کدام بخش‌ها نیاز به به‌روزرسانی دارند و از اعمال تغییرات غیرضروری در DOM جلوگیری می‌کند. این فرایند باعث می‌شود که رندرهای غیرضروری کاهش یابند و تنها عناصری که تغییر کرده‌اند، مجدداً در DOM به‌روزرسانی شوند.

روش‌های بهینه‌سازی رندر و کاهش رندرهای غیرضروری

استفاده از React.memo برای کامپوننت‌های فانکشنال: React.memo یک HOC است که از رندر مجدد کامپوننت‌های فانکشنال در صورت عدم تغییر پروپ‌ها جلوگیری می‌کند. این ابزار بسیار مفید است به خصوص در مواقعی که یک کامپوننت بدون نیاز واقعی به تغییر، مجدداً رندر می‌شود. با استفاده از React.memo می‌توان عملکرد برنامه را بهبود داد.

import React from 'react';

const MyComponent = React.memo(({ name }) => {
    console.log('رندر شد');
    return <p>نام کاربر: {name}</p>;
});

export default MyComponent;

در این مثال، MyComponent تنها زمانی دوباره رندر می‌شود که پروپ name تغییر کند، و اگر پروپ تغییر نکرده باشد، React از رندر مجدد آن جلوگیری می‌کند.

استفاده از useMemo و useCallback برای جلوگیری از محاسبات سنگین

useMemo: این هوک نتایج محاسبات سنگین یا پیچیده را ذخیره کرده و تنها زمانی که وابستگی‌ها تغییر کنند، دوباره محاسبه می‌شود.
useCallback: این هوک برای بهینه‌سازی فراخوانی توابع استفاده می‌شود و تضمین می‌کند که تابع تنها در صورت تغییر وابستگی‌ها دوباره تعریف شود.

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

function ExpensiveComponent({ items }) {
    const [count, setCount] = useState(0);

    const totalValue = useMemo(() => {
        console.log('محاسبه مجموع...');
        return items.reduce((acc, item) => acc + item.value, 0);
    }, [items]);

    const increment = useCallback(() => setCount(count + 1), [count]);

    return (
        <div>
            <p>مجموع: {totalValue}</p>
            <button onClick={increment}>افزایش شمارش</button>
        </div>
    );
}

در این مثال، totalValue فقط زمانی محاسبه می‌شود که items تغییر کند و تابع increment تنها در صورت تغییر count تعریف مجدد می‌شود، که باعث کاهش محاسبات غیرضروری می‌شود.

استفاده از shouldComponentUpdate و PureComponent در کامپوننت‌های کلاسی

shouldComponentUpdate: این متد به شما اجازه می‌دهد که شرایط رندر مجدد یک کامپوننت کلاس را تعیین کنید. اگر متد shouldComponentUpdate مقدار false برگرداند، کامپوننت از رندر مجدد جلوگیری می‌کند.
PureComponent: PureComponent مشابه کامپوننت‌های معمولی است با این تفاوت که به طور خودکار مقایسه سطحی (shallow comparison) بین پروپ‌ها و state را انجام می‌دهد و فقط زمانی که تفاوتی وجود داشته باشد، رندر مجدد را اجرا می‌کند.

import React, { PureComponent } from 'react';

class MyPureComponent extends PureComponent {
    render() {
        console.log('رندر شد');
        return <p>نام کاربر: {this.props.name}</p>;
    }
}

export default MyPureComponent;

در این مثال، MyPureComponent تنها زمانی رندر می‌شود که پروپ‌ها تغییر کنند، که از رندرهای غیرضروری جلوگیری می‌کند.

Virtualization برای لیست‌های بزرگ: اگر لیست‌های بزرگ و پیچیده‌ای دارید که تمامی آیتم‌ها به صورت همزمان نمایش داده می‌شوند، می‌توانید از تکنیک Virtualization برای رندر فقط آیتم‌های قابل مشاهده استفاده کنید. این تکنیک با استفاده از کتابخانه‌هایی مانند react-window و react-virtualized، به طور موثری تعداد رندرها را کاهش می‌دهد و عملکرد را بهبود می‌بخشد.

import { FixedSizeList as List } from 'react-window';

const MyList = ({ items }) => (
    <List
        height={300}
        itemCount={items.length}
        itemSize={35}
        width="100%"
    >
        {({ index, style }) => (
            <div style={style}>
                {items[index].name}
            </div>
        )}
    </List>
);

کاهش رندرهای درختی با Context API: هنگامی که از Context API برای مدیریت وضعیت استفاده می‌کنید، مطمئن شوید که بهینه‌ترین ساختار را انتخاب کرده‌اید. اگر یک context برای همه کامپوننت‌ها فراهم شده و این context تغییر کند، تمامی کامپوننت‌های فرزند نیز رندر خواهند شد. برای حل این مشکل می‌توانید context‌های خاص‌تری تعریف کنید یا از ابزارهایی مانند useReducer و useContext در کنار هم استفاده کنید تا میزان رندرهای غیرضروری کاهش یابد. بهینه‌سازی رندر در React با استفاده از تکنیک‌های مختلفی مانند React.memo، useMemo، useCallback، و PureComponent و تکنیک‌های مجازی‌سازی (Virtualization) کمک می‌کند تا از رندرهای غیرضروری جلوگیری شود و عملکرد برنامه بهبود یابد. با استفاده از این روش‌ها، React می‌تواند تغییرات را به‌طور دقیق شناسایی کند و فقط بخش‌هایی که نیاز به به‌روزرسانی دارند را رندر کند. این امر باعث کاهش مصرف منابع و بهبود تجربه کاربری در پروژه‌های بزرگ و پیچیده می‌شود.

نتیجه‌گیری

آموزش‌های پیشرفته (Advanced Guides) React به توسعه‌دهندگان این امکان را می‌دهد تا از قابلیت‌های کامل و بهینه این کتابخانه در پروژه‌های بزرگ و پیچیده بهره ببرند. با یادگیری تکنیک‌های بهینه‌سازی عملکرد، مدیریت داده با Context API و Redux، و استفاده از الگوهایی مانند Render Props و Higher-Order Components، می‌توانیم اپلیکیشن‌هایی مقیاس‌پذیر، سریع و با تجربه کاربری بهتر ایجاد کنیم. این مهارت‌ها نه تنها به بهبود کارایی و ساختار کد کمک می‌کنند، بلکه مسیر رشد به عنوان یک توسعه‌دهنده حرفه‌ای را هموار می‌سازند.

آموزش‌های پیشرفته (Advanced Guides) React

دیدگاه های شما

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *