المتطلبات الأساسية
قبل البدء، تأكد من توفر الآتي:
- Node.js 20 أو أحدث
- مشروع Next.js 15+ يستخدم App Router
- معرفة أساسية بـ React Hooks وكيفية إعادة رسم المكوّنات
ما الذي ستبنيه
بنهاية هذا الدرس ستكون قادرًا على:
- دمج React Scan في بيئة تطوير Next.js الخاصة بك
- فهم كيفية قراءة الطبقة البصرية التفاعلية
- إصلاح مشكلة أداء حقيقية باستخدام React Compiler أو الحفظ اليدوي
- إنشاء مراقب
onRenderقابل لإعادة الاستخدام في أي مشروع
ما هو React Scan؟
React Scan أداة تشخيص أداء لتطبيقات React لا تحتاج إلى أي إعداد مسبق. تلتفّ حول محرّك React الداخلي وتضيء كل مكوّن يُعاد رسمه على الشاشة — دون إضافة بلاغن Babel أو جلسات Profiler أو تخمين.
كل إضاءة تخبرك بـ:
- حدود برتقالية — رُسم المكوّن في هذه الدورة
- حدود رمادية — رُسم المكوّن لكنه لم ينتج أي تغيير في DOM (إعادة رسم غير ضرورية)
- شارة عدد الرسم — عدد مرات رسم المكوّن منذ تحميل الصفحة
على عكس React DevTools Profiler، يعمل React Scan باستمرار خلال التطوير. تشاهد عمليات إعادة الرسم لحظة حدوثها.
الخطوة 1: تثبيت React Scan
أضف الحزمة ضمن تبعيات التطوير:
npm install react-scan --save-dev
# أو
pnpm add -D react-scanالخطوة 2: إنشاء مكوّن ReactScanProvider
في Next.js App Router، التخطيط الجذري هو Server Component — لا يمكن استدعاء scan() على مستوى الوحدة هناك. بدلًا من ذلك، استخدم خطاف useScan داخل Client Component مخصص.
أنشئ الملف components/ReactScanProvider.tsx:
"use client";
import { useScan } from "react-scan";
export function ReactScanProvider() {
useScan({
enabled: process.env.NODE_ENV === "development",
log: false,
showToolbar: true,
animationSpeed: "fast",
trackUnnecessaryRenders: true,
showFPS: true,
showNotificationCount: true,
});
return null;
}يُهيّئ useScan الماسح على جانب العميل فقط، مما يجعله آمنًا في سياق SSR ولا يؤثر أبدًا في HTML المُصيَّر على الخادم.
الخطوة 3: تركيب المزوّد في التخطيط الجذري
افتح app/layout.tsx وأضف ReactScanProvider كأول عنصر داخل <body>:
import { ReactScanProvider } from "@/components/ReactScanProvider";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="ar" dir="rtl">
<body>
<ReactScanProvider />
{children}
</body>
</html>
);
}شغّل خادم التطوير بـ npm run dev وافتح المتصفح. ستظهر شريط أدوات عائم في الركن الأيمن السفلي وومضات برتقالية فوق المكوّنات التي تُعاد رسمها.
الخطوة 4: قراءة الطبقة البصرية
الإضاءات البرتقالية
تظهر عند كل مرة يعمل فيها دالة رسم المكوّن. هذا أمر طبيعي ومتوقع — الهدف ليس إلغاء كل عمليات إعادة الرسم، بل فقط غير الضرورية منها.
الإضاءات الرمادية
تعني أن المكوّن أُعيد رسمه لكن ناتجه كان مطابقًا للرسم السابق. يسمّي React Scan هذه إعادات رسم غير ضرورية. تستهلك وقت المعالج دون تحديث واجهة المستخدم.
شريط الأدوات العائم
يعرض الشريط:
- عداد FPS — الإطارات في الثانية؛ أي قيمة دون 30 تشير إلى واجهة متذبذبة
- شارة الإشعارات — عدد عمليات الرسم غير الضرورية منذ تحميل الصفحة
الخطوة 5: استنساخ نمط خاطئ شائع
لنبنِ مثالًا واقعيًا. أنشئ app/dashboard/page.tsx:
import { UserCard } from "@/components/UserCard";
import { StatsPanel } from "@/components/StatsPanel";
export default function DashboardPage() {
return (
<main>
<UserCard user={{ name: "أحمد", role: "admin" }} />
<StatsPanel config={{ refreshInterval: 5000 }} />
</main>
);
}أنشئ components/UserCard.tsx:
"use client";
import { useState } from "react";
type User = { name: string; role: string };
export function UserCard({ user }: { user: User }) {
const [count, setCount] = useState(0);
return (
<div>
<p>{user.name} — {user.role}</p>
<button onClick={() => setCount((c) => c + 1)}>
نقر {count} مرات
</button>
</div>
);
}وأنشئ components/StatsPanel.tsx:
"use client";
type Config = { refreshInterval: number };
export function StatsPanel({ config }: { config: Config }) {
return <div>يتحدث كل {config.refreshInterval} ميلي ثانية</div>;
}اضغط على الزر مرات عدة. يُضيء React Scan UserCard برتقاليًا في كل ضغطة — متوقع لأن الحالة المحلية تغيّرت. لكن StatsPanel يومض رماديًا: يُعاد رسمه مع كل إعادة رسم للمكوّن الأب رغم أن قيم config لم تتغير. المشكلة هي الكائن الحرفي المُضمَّن { refreshInterval: 5000 } — ينشئ JavaScript مرجعًا جديدًا في كل دورة رسم.
الخطوة 6: إصلاح إعادات الرسم غير الضرورية
الخيار أ — useMemo (يدوي)
استقرار المرجع في DashboardPage:
"use client";
import { useMemo } from "react";
import { UserCard } from "@/components/UserCard";
import { StatsPanel } from "@/components/StatsPanel";
export default function DashboardPage() {
const statsConfig = useMemo(() => ({ refreshInterval: 5000 }), []);
return (
<main>
<UserCard user={{ name: "أحمد", role: "admin" }} />
<StatsPanel config={statsConfig} />
</main>
);
}أعد تحميل الصفحة. اختفت الإضاءة الرمادية على StatsPanel.
الخيار ب — React Compiler (تلقائي، موصى به)
إذا كان مشروعك يستخدم React Compiler 1.0 (مستقر منذ أكتوبر 2025، مُختبَر في Meta)، فلن تحتاج إلى useMemo أبدًا. يكتشف المترجم القيم الثابتة ويحفظها تلقائيًا وقت البناء.
فعّله في next.config.ts لـ Next.js 16:
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
reactCompiler: true,
};
export default nextConfig;أعد البناء وافتح المتصفح. تختفي الإضاءات الرمادية على StatsPanel دون تعديل كود المكوّن.
الخطوة 7: المراقبة البرمجية باستخدام onRender
لميزانيات الأداء الآلية، استخدم خاصية onRender لتسجيل أو التحقق من عدد عمليات الرسم:
"use client";
import { useScan } from "react-scan";
import type { Fiber, Render } from "react-scan";
export function ReactScanProvider() {
useScan({
enabled: process.env.NODE_ENV === "development",
log: false,
showToolbar: true,
trackUnnecessaryRenders: true,
onRender: (fiber: Fiber, renders: Array<Render>) => {
const componentName =
(fiber.type as { displayName?: string; name?: string })
?.displayName ??
(fiber.type as { name?: string })?.name ??
"مجهول";
if (renders.length > 5) {
console.warn(
`[react-scan] ${componentName} رُسم ${renders.length} مرات في دورة واحدة`
);
}
},
});
return null;
}يسجّل هذا تحذيرًا في الكونسول عند تجاوز عدد رسم مكوّن ما 5 مرات في دورة commit واحدة.
الخطوة 8: التفعيل بمعامل URL لبيئة الاختبار
لمشاركة رابط تشخيص مع زميل دون تفعيل الماسح للجميع في بيئة staging، عدّل ReactScanProvider ليستجيب لمعامل ?scan=true:
"use client";
import { useScan } from "react-scan";
function isScanEnabled() {
if (process.env.NODE_ENV === "development") return true;
if (typeof window === "undefined") return false;
return new URLSearchParams(window.location.search).get("scan") === "true";
}
export function ReactScanProvider() {
useScan({
enabled: isScanEnabled(),
showToolbar: true,
trackUnnecessaryRenders: true,
});
return null;
}أضف ?scan=true لأي رابط في بيئة staging لتفعيل الطبقة البصرية عند الطلب.
الخطوة 9: بديل وسم Script من CDN
إذا كنت تفضّل تجنّب تبعية npm، حمّل React Scan عبر مكوّن Script الخاص بـ Next.js:
import Script from "next/script";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="ar" dir="rtl">
<body>
{process.env.NODE_ENV === "development" && (
<Script
src="https://unpkg.com/react-scan/dist/auto.global.js"
strategy="beforeInteractive"
/>
)}
{children}
</body>
</html>
);
}يعمل بناء auto.global.js تلقائيًا دون أي إعداد. مثالي للتحقيق السريع، لكن حزمة npm تمنحك تحكمًا كاملًا عبر useScan وonRender.
استكشاف الأخطاء وإصلاحها
لا يظهر شريط الأدوات
تأكد أن ReactScanProvider مكوّن "use client" ويُرسم داخل <body> قبل محتوى الصفحة.
كل المكوّنات تومض برتقاليًا عند كل ضغطة مفتاح
هذا متوقع للحقول المتحكَّم بها. ركّز أولًا على الإضاءات الرمادية — تلك هي عمليات الرسم غير الضرورية القابلة للإصلاح.
React Scan يُبطئ خادم التطوير
اضبط animationSpeed: "off" لتعطيل الحركات البصرية مع الإبقاء على onRender نشطًا. يقلّل هذا الحمل البصري بشكل ملحوظ.
useScan غير معرّف في وقت التشغيل
تأكد من استيرادك من "react-scan" (حزمة npm) وليس من نص CDN. واجهة برمجة الخطافات متاحة فقط من حزمة npm.
الخطوات التالية
- ادمج React Scan مع React DevTools Profiler لتحليل عميق للرسومات المكلفة.
- اربط دالة
onRenderبتحقق CI لإيقاف البناء عند تجاوز مكوّن حرج عدد رسومات محدد. - اقرأ الدليل المصاحب حول React Compiler والحفظ التلقائي لمعرفة كيف يُزيل المترجم عمليات الرسم التي يكشفها React Scan.
- استخدم React Scan مع TanStack Query لرصد اشتراكات حالة الخادم التي تُطلق إعادات رسم أكثر من المتوقع.
الخلاصة
يمنحك React Scan طبقة بصرية دائمة التشغيل تحوّل إعادات رسم React من أحداث غير مرئية إلى إضاءات لامعة وقابلة للعدّ. باتباع هذا الدرس، أصبحت قادرًا على تثبيته في أي مشروع Next.js App Router، وفهم إشارات الإضاءة البرتقالية والرمادية، وإصلاح إعادات الرسم غير الضرورية يدويًا بـ useMemo أو تلقائيًا بـ React Compiler، وإعداد مراقب onRender برمجي يكتشف التراجعات قبل وصولها إلى الإنتاج.