بازگشت به وبلاگ
۱۴۰۵ آبان

React Compiler چیست؟ آیا دیگر به useMemo و useCallback نیاز نداریم؟

React Compiler ابزاری رسمی برای بهینه‌سازی خودکار اپلیکیشن‌های React است. در این مقاله با نحوه عملکرد آن، Automatic Memoization، تفاوتش با useMemo و useCallback، راه‌اندازی در Vite و Next.js و محدودیت‌های آن آشنا می‌شویم.

  • React
  • React Compiler
  • JavaScript
  • TypeScript
  • Performance
  • Frontend
  • Optimization
React Compiler چیست؟ آیا دیگر به useMemo و useCallback نیاز نداریم؟

React Compiler چیست؟ آیا دیگر به useMemo و useCallback نیاز نداریم؟

مقدمه

یکی از چالش‌های همیشگی توسعه‌دهندگان React، مدیریت رندرهای مجدد (Re-renders) و جلوگیری از محاسبات غیرضروری در کامپوننت‌هاست.

برای حل این مسئله، سال‌ها از ابزارهایی مانند useMemo، useCallback و React.memo استفاده کرده‌ایم. این ابزارها قدرتمند هستند، اما استفاده نادرست یا بیش‌ازحد از آن‌ها می‌تواند کد را پیچیده‌تر و نگهداری آن را دشوارتر کند.

React Compiler با رویکردی متفاوت وارد شده است: به‌جای اینکه توسعه‌دهنده به‌صورت دستی مشخص کند چه چیزی باید Memoize شود، Compiler کد را تحلیل می‌کند و بسیاری از بهینه‌سازی‌ها را به‌صورت خودکار انجام می‌دهد.

React Compiler در اکتبر ۲۰۲۵ به نسخه پایدار 1.0 رسید و اکنون می‌توان از آن در پروژه‌های واقعی استفاده کرد.

در این مقاله بررسی می‌کنیم که React Compiler دقیقاً چیست، چگونه کار می‌کند، چه تفاوتی با Memoization دستی دارد و آیا واقعاً می‌توانیم استفاده از Hookهای مربوط به Memoization را کنار بگذاریم یا نه.

React Compiler چیست؟

React Compiler یک ابزار بهینه‌سازی در زمان Build است که کد کامپوننت‌ها و Hookهای React را تحلیل می‌کند و به‌صورت خودکار Memoization را در بخش‌های قابل‌بهینه‌سازی اعمال می‌کند.

به زبان ساده، Compiler تلاش می‌کند از اجرای دوباره محاسبات و رندرهایی که نتیجه جدیدی تولید نمی‌کنند جلوگیری کند؛ بدون اینکه مجبور باشیم برای هر کامپوننت از memo یا برای هر مقدار و تابع از Hookهای Memoization استفاده کنیم.

برای درک بهتر، کد زیر را در نظر بگیرید:

import { useMemo, useCallback, memo } from "react";

const ProductList = memo(function ProductList({
  products,
  onSelect,
}: {
  products: Product[];
  onSelect: (id: string) => void;
}) {
  const sortedProducts = useMemo(
    () => products.toSorted((a, b) => a.price - b.price),
    [products]
  );

  const handleSelect = useCallback(
    (id: string) => {
      onSelect(id);
    },
    [onSelect]
  );

  return (
    <ul>
      {sortedProducts.map((product) => (
        <li key={product.id}>
          <button onClick={() => handleSelect(product.id)}>
            {product.name}
          </button>
        </li>
      ))}
    </ul>
  );
});

در این مثال، برای کنترل رندرهای غیرضروری و حفظ ارجاع مقادیر و توابع از سه ابزار مختلف استفاده کرده‌ایم.

با فعال بودن React Compiler، در بسیاری از موارد می‌توانیم کد را ساده‌تر بنویسیم:

function ProductList({
  products,
  onSelect,
}: {
  products: Product[];
  onSelect: (id: string) => void;
}) {
  const sortedProducts = products.toSorted(
    (a, b) => a.price - b.price
  );

  const handleSelect = (id: string) => {
    onSelect(id);
  };

  return (
    <ul>
      {sortedProducts.map((product) => (
        <li key={product.id}>
          <button onClick={() => handleSelect(product.id)}>
            {product.name}
          </button>
        </li>
      ))}
    </ul>
  );
}
اشتراک‌گذاریاشتراک در ایکساشتراک در تلگرام

آخرین به‌روزرسانی: ۱۴۰۵ آبان

ا

امیر اسماعیل زاده

توسعه دهنده فرانت اند

توسعه‌دهنده فرانت‌اند با بیش از 3 سال تجربه در ساخت وب‌سایت‌ها و اپلیکیشن‌های وب مدرن و واکنش‌گرا، با تمرکز بر React، Next.js، بهینه‌سازی عملکرد و تجربه کاربری.

پست قبلیپشت صحنه React Server Components؛ از اجرای سرور تا تعامل در مرورگرپست بعدیTanStack Redact چیست؟ جایگزینی کوچک‌تر برای React با تمرکز بر Performance

پست‌های مرتبط

  • پشت صحنه React Server Components؛ از اجرای سرور تا تعامل در مرورگر۱۴۰۵ آبان
  • TanStack Redact چیست؟ جایگزینی کوچک‌تر برای React با تمرکز بر Performance۱۴۰۵ آبان
EN

Compiler می‌تواند در زمان Build این کد را تحلیل کرده و Memoization مناسب را به‌صورت خودکار اضافه کند.

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

Automatic Memoization چگونه کار می‌کند؟

برای فهم عملکرد React Compiler ابتدا باید با مفهوم Memoization آشنا باشیم.

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

React Compiler این کار را در زمان Build انجام می‌دهد.

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

این فرآیند می‌تواند شامل موارد زیر باشد:

  • جلوگیری از رندر مجدد غیرضروری کامپوننت‌های فرزند
  • Cache کردن مقادیر محاسبه‌شده
  • حفظ ارجاع توابع و مقادیر در شرایط مناسب
  • تشخیص وابستگی‌های واقعی در کد
  • بهینه‌سازی بخش‌های مختلف یک کامپوننت، نه صرفاً کل کامپوننت

یکی از تفاوت‌های مهم آن با Memoization دستی این است که Compiler می‌تواند بخش‌های مختلف یک تابع را به‌صورت دقیق‌تر و حتی شرطی بهینه کند.

تفاوت زمان اجرا و زمان Build

ویژگیMemoization دستیReact Compiler
زمان تصمیم‌گیریهنگام نوشتن کدهنگام Build
نیاز به Hookمعمولاً بلهدر بسیاری از موارد خیر
مدیریت Dependencyتوسط توسعه‌دهندهبا تحلیل Compiler
نگهداری کدممکن است پیچیده شودمعمولاً ساده‌تر
کنترل دقیقمستقیم و دستیخودکار و مبتنی بر تحلیل

این جدول به معنی برتری مطلق Compiler در تمام شرایط نیست؛ بلکه نشان‌دهنده تفاوت رویکرد این دو روش است.

آیا دیگر به useMemo و useCallback نیاز نداریم؟

پاسخ کوتاه: در بسیاری از موارد خیر، اما این Hookها منسوخ نشده‌اند.

هدف React Compiler کاهش نیاز به Memoization دستی است، نه حذف کامل ابزارهای قبلی.

useMemo

useMemo نتیجه یک محاسبه را بین رندرها Cache می‌کند.

const filteredUsers = useMemo(() => {
  return users.filter((user) => user.active);
}, [users]);

با React Compiler، در بسیاری از موارد می‌توانیم همان منطق را بدون useMemo بنویسیم:

const filteredUsers = users.filter((user) => user.active);

Compiler می‌تواند تشخیص دهد که این محاسبه در چه شرایطی نیاز به اجرای مجدد دارد.

useCallback

useCallback برای حفظ ارجاع یک تابع بین رندرها استفاده می‌شود.

const handleSubmit = useCallback(() => {
  submitForm(formData);
}, [formData]);

با React Compiler:

const handleSubmit = () => {
  submitForm(formData);
};

در بسیاری از شرایط Compiler می‌تواند ارجاع تابع را به‌صورت خودکار مدیریت کند.

React.memo

React.memo از رندر مجدد یک کامپوننت در شرایطی که Props آن تغییر نکرده‌اند جلوگیری می‌کند.

const UserCard = memo(function UserCard({ user }: Props) {
  return <div>{user.name}</div>;
});

React Compiler می‌تواند به‌صورت خودکار بهینه‌سازی مشابهی را برای کامپوننت‌های قابل‌بهینه‌سازی اعمال کند.

پس چه زمانی هنوز به این ابزارها نیاز داریم؟

چند نکته مهم وجود دارد:

  1. زمانی که به کنترل دقیق‌تری روی Cache شدن یک مقدار یا تابع نیاز داریم.
  2. زمانی که پایداری یک مقدار برای Dependencyهای یک Effect اهمیت دارد.
  3. زمانی که پروژه هنوز Compiler را فعال نکرده است.
  4. زمانی که در حال نگهداری کد قدیمی هستیم و حذف Memoization موجود بدون تست می‌تواند رفتار یا خروجی بهینه‌سازی را تغییر دهد.

توصیه مهم: در کدهای جدید ابتدا به Compiler اعتماد کنید و فقط در مواردی که به کنترل دقیق‌تری نیاز دارید از useMemo و useCallback استفاده کنید. در پروژه‌های موجود نیز قبل از حذف Memoizationهای قبلی، رفتار و Performance را آزمایش کنید.

راه‌اندازی React Compiler

React Compiler با React 19 بهترین تجربه را ارائه می‌دهد، اما از React 17 و 18 نیز پشتیبانی می‌کند؛ البته ممکن است برای نسخه‌های قدیمی‌تر به تنظیمات و Runtime مناسب نیاز داشته باشید.

برای راه‌اندازی، ابتدا پکیج Compiler را نصب می‌کنیم:

npm install -D babel-plugin-react-compiler

یا با pnpm:

pnpm add -D babel-plugin-react-compiler

راه‌اندازی در Vite

در نسخه‌های جدید @vitejs/plugin-react، مستندات رسمی React استفاده از reactCompilerPreset را همراه با @rolldown/plugin-babel نشان می‌دهد.

ابتدا پکیج موردنیاز را نصب کنید:

npm install -D @rolldown/plugin-babel

سپس فایل vite.config.ts را به شکل زیر تنظیم کنید:

import { defineConfig } from "vite";
import react, {
  reactCompilerPreset,
} from "@vitejs/plugin-react";
import babel from "@rolldown/plugin-babel";

export default defineConfig({
  plugins: [
    react(),
    babel({
      presets: [reactCompilerPreset()],
    }),
  ],
});

توجه کنید که این روش مربوط به نسخه‌های جدید پلاگین React در Vite است. در نسخه‌های قدیمی‌تر، تنظیمات Babel متفاوت است؛ بنابراین بهتر است نسخه پکیج‌های پروژه را با مستندات رسمی تطبیق دهید.

راه‌اندازی در Next.js

در Next.js 16، پشتیبانی از React Compiler پایدار شده و دیگر به تنظیمات Experimental نیاز نداریم.

ابتدا پکیج را نصب کنید:

npm install -D babel-plugin-react-compiler

سپس در next.config.ts:

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  reactCompiler: true,
};

export default nextConfig;

با همین تنظیم، Next.js Compiler را در Build فعال می‌کند.

در نسخه‌های قبلی Next.js ممکن است تنظیمات یا وضعیت پشتیبانی متفاوت باشد؛ برای نمونه مستندات Next.js پشتیبانی از این قابلیت را از نسخه 15.3.1 نیز توضیح داده‌اند.

چگونه مطمئن شویم Compiler فعال است؟

صرف نصب پکیج به معنی فعال بودن Compiler نیست.

برای بررسی عملکرد آن می‌توانیم از React Developer Tools استفاده کنیم:

  1. افزونه React Developer Tools را نصب کنید.
  2. پروژه را در حالت Development اجرا کنید.
  3. وارد بخش Components شوید.
  4. کامپوننت‌های بهینه‌سازی‌شده را بررسی کنید.

در کامپوننت‌هایی که توسط Compiler بهینه شده‌اند، ممکن است نشانگر Memo ✨ را مشاهده کنید.

همچنین می‌توانید خروجی Build را بررسی کنید. کد تولیدشده ممکن است شامل Importهایی از react/compiler-runtime و منطق Cache تولیدشده توسط Compiler باشد.

Incremental Adoption؛ فعال‌سازی تدریجی در پروژه‌های واقعی

اگر پروژه بزرگی دارید، پیشنهاد نمی‌شود بدون بررسی و تست، Compiler را یک‌باره روی تمام کدهای Production فعال کنید.

React Compiler امکان Adoption تدریجی را فراهم می‌کند.

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

یکی از روش‌ها استفاده از compilationMode: "annotation" است.

در این حالت فقط کامپوننت‌ها و Hookهایی که صراحتاً علامت‌گذاری شده‌اند، وارد فرآیند Compilation می‌شوند.

function ProductList({ products }: Props) {
  "use memo";

  const sortedProducts = products.toSorted(
    (a, b) => a.price - b.price
  );

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

برای خارج کردن یک کامپوننت از فرآیند بهینه‌سازی نیز می‌توان از دستور زیر استفاده کرد:

function LegacyComponent() {
  "use no memo";

  // This component will be skipped by the compiler.
  return <div>Legacy component</div>;
}

این روش‌ها به‌خصوص برای پروژه‌های بزرگ و کدبیس‌های قدیمی مفید هستند.

محدودیت‌ها و نکات مهم

با وجود مزایای React Compiler، نباید آن را یک راه‌حل جادویی برای تمام مشکلات Performance بدانیم.

۱. Compiler جایگزین معماری درست نیست

اگر یک کامپوننت بیش از حد بزرگ باشد، State در سطح نامناسبی نگهداری شود یا داده‌های غیرضروری وارد درخت کامپوننت شوند، Compiler لزوماً مشکل معماری را حل نمی‌کند.

۲. قوانین React همچنان اهمیت دارند

Compiler برای تحلیل صحیح کد به رعایت Rules of React وابسته است.

مواردی مانند تغییر مستقیم Props یا State، استفاده نادرست از Hookها و Side Effect در زمان Render می‌توانند باعث شوند بخشی از کد قابل‌بهینه‌سازی نباشد.

۳. Build ممکن است کمی کندتر شود

از آنجا که Compiler باید کد را تحلیل و بهینه کند، ممکن است زمان Build و برخی فرآیندهای Development افزایش پیدا کند.

این هزینه باید در کنار بهبود احتمالی تجربه کاربر سنجیده شود.

۴. تمام مشکلات Performance با Memoization حل نمی‌شوند

React Compiler روی بهینه‌سازی Render و محاسبات مرتبط با آن تمرکز دارد. اما مشکلاتی مانند موارد زیر نیازمند راهکارهای جداگانه هستند:

  • Network Bottlenecks
  • Large Bundle Size
  • Expensive API Requests
  • Unoptimized Images
  • Poor Data Fetching Strategies
  • Long Tasks
  • Inefficient DOM Operations

به همین دلیل همچنان باید از ابزارهایی مانند React Profiler و Chrome Performance Panel استفاده کنیم.

آیا React Compiler باعث افزایش سرعت همه پروژه‌ها می‌شود؟

خیر. میزان بهبود به ساختار پروژه، تعداد رندرهای غیرضروری، محاسبات سنگین و الگوی استفاده از React بستگی دارد.

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

از طرف دیگر، در پروژه‌هایی که Memoization دستی زیادی دارند یا کامپوننت‌های فرزند زیادی به‌صورت غیرضروری رندر می‌شوند، Compiler می‌تواند تأثیر قابل‌توجهی داشته باشد.

برای ارزیابی واقعی، قبل و بعد از فعال‌سازی Compiler را با شرایط یکسان اندازه‌گیری کنید.

جمع‌بندی

React Compiler یکی از مهم‌ترین تغییرات در تجربه توسعه React است. این ابزار تلاش می‌کند بخش زیادی از پیچیدگی Memoization دستی را از دوش توسعه‌دهندگان بردارد و به آن‌ها اجازه دهد کد ساده‌تر و خواناتری بنویسند.

بااین‌حال، Compiler جایگزین درک عمیق React، طراحی صحیح Componentها، مدیریت درست State و اندازه‌گیری Performance نیست.

پیشنهاد من این است:

  • در پروژه‌های جدید React 19، فعال‌سازی Compiler را جدی بررسی کنید.
  • در پروژه‌های موجود، از Adoption تدریجی استفاده کنید.
  • Memoizationهای قبلی را بدون تست حذف نکنید.
  • برای بررسی نتایج واقعی، از Profiler و ابزارهای Performance استفاده کنید.

در نهایت، هدف این نیست که صرفاً تعداد useMemo و useCallbackهای پروژه را به صفر برسانیم؛ هدف این است که با کد ساده‌تر، عملکرد بهتر و پیچیدگی نگهداری کمتر به دست آوریم.

منابع و مطالعه بیشتر

  • React Compiler — Official Documentation
  • React Compiler Installation
  • React Compiler Incremental Adoption
  • React Compiler v1.0 Announcement
  • Next.js React Compiler Configuration
  • React useMemo
  • React useCallback
  • React memo

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