<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>نوشته های مهدی فرجی</title>
        <link>https://virgool.io/feed/@mehdifaraji</link>
        <description></description>
        <language>fa</language>
        <pubDate>2026-04-14 14:37:04</pubDate>
        <image>
            <url>https://files.virgool.io/upload/users/1981516/avatar/SXy5T6.jpg?height=120&amp;width=120</url>
            <title>مهدی فرجی</title>
            <link>https://virgool.io/@mehdifaraji</link>
        </image>

                    <item>
                <title>راهنمای useEffect و Cleanup توی React</title>
                <link>https://virgool.io/@mehdifaraji/%D8%B1%D8%A7%D9%87%D9%86%D9%85%D8%A7%DB%8C-useeffect-%D9%88-cleanup-%D8%AA%D9%88%DB%8C-react-hbfg5otaowok</link>
                <description>React useEffect hook cleanupببین وقتی توی React از useEffect استفاده میکنیم، معمولاً داریم یه کار جانبی انجام میدیم. مثلا:تایمر میذاریمبه یه event گوش میدیمدرخواست API میزنیمWebSocket باز میکنیمsubscription میسازیمحالا نکته مهم چیه؟اینه که وقتی کامپوننت از بین میره (Unmount میشه) یا دوباره قراره effect اجرا بشه، باید اون چیزایی که ساختیم رو پاک کنیم.اگه پاکشون نکنیم چی میشه؟اپ کند میشهmemory leak میخوریمارورهای عجیب میادstate روی کامپوننتی که دیگه وجود نداره ست میشهاصلاً Cleanup یا همون پاک کردن چیه؟ساختار useEffect اینجوریه:useEffect(() =&gt; {
  // کار اصلی اینجاست

  return () =&gt; {
    // این قسمت همون cleanup هست
  };
}, []);اون تابعی که داخل return برمیگردونی، همون تمیزکاریه. React هر وقت بخواد effect رو ببنده یا دوباره اجرا کنه، اول cleanup قبلی رو صدا میزنه.تایمر گذاشتی؟useEffect(() =&gt; {
  const interval = setInterval&#40;(&#41; =&gt; {
    console.log(&quot;hello&quot;);
  }, 2000);

  return () =&gt; clearInterval(interval);
}, []);اگه clearInterval نزنی، حتی بعد از اینکه از صفحه رفتی بیرون، تایمر همچنان اجرا میشه.بعد میگی چرا لاگ هنوز میاد؟ چرا باتری خالی میشه؟ چرا اپ کند شده؟هر setInterval یا setTimeout = حتماً clear کنEvent Listener ساختی؟توی React خیلی وقتا به event ها گوش میدیم. مثلا کیبورد:useEffect(() =&gt; {
  const showSub = Keyboard.addListener(&quot;keyboardDidShow&quot;, () =&gt; {});
  const hideSub = Keyboard.addListener(&quot;keyboardDidHide&quot;, () =&gt; {});

  return () =&gt; {
    showSub.remove();
    hideSub.remove();
  };
}, []);اگه remove نکنی، بعداً هم اجرا میشه حتی وقتی اون صفحه دیگه وجود نداره. این باعث رفتارهای غیرقابل پیشبینی میشه. هر addListener = یه remove لازم داره.درخواست API زدی؟فرض کن اینو نوشتی:useEffect(() =&gt; {
  fetch(&quot;https://example.com&quot;)
    .then(res =&gt; res.json())
    .then(data =&gt; setData(data));
}, []);حالا کاربر سریع از صفحه میره بیرون ولی درخواست هنوز در حال انجامه. وقتی جواب بیاد، میخواد setData بزنه روی کامپوننتی که دیگه وجود نداره.اینجاست که ارور معروف میاد:Can&#039;t perform a React state update on an unmounted componentراه حل چیه؟ AbortControlleruseEffect(() =&gt; {
  const controller = new AbortController();

  fetch(&quot;https://example.com&quot;, {
    signal: controller.signal
  })
    .then(res =&gt; res.json())
    .then(data =&gt; setData(data))
    .catch(err =&gt; {
      if (err.name !== &quot;AbortError&quot;) {
        console.error(err);
      }
    });

  return () =&gt; controller.abort();
}, []);اینجوری وقتی صفحه بسته بشه، درخواست هم لغو میشه.WebSocket باز کردی؟اگه WebSocket باز کنی و نبندیش، اتصال همچنان باز میمونه و دیتا رد و بدل میشه.useEffect(() =&gt; {
  const ws = new WebSocket(&quot;wss://example.com&quot;);

  ws.onmessage = (event) =&gt; {
    console.log(event.data);
  };

  return () =&gt; {
    ws.close();
  };
}, []);خیلی از باگهای اپهای realtime از همینجا میاد.هر اتصال = یه close لازم دارهچند تا cleanup داری؟بعضی وقتا cleanup شلوغ میشه:return () =&gt; {
  sub1.remove();
  sub2.remove();
  clearInterval(timer);
};میتونی تمیزترش کنی:useEffect(() =&gt; {
  const cleanups = [];

  const sub = something();
  cleanups.push(() =&gt; sub.remove());

  const timer = setInterval&#40;(&#41; =&gt; {}, 1000);
  cleanups.push(() =&gt; clearInterval(timer));

  return () =&gt; {
    cleanups.forEach(fn =&gt; fn());
  };
}, []);Cleanup کی اجرا میشه؟دو حالت داره:۱. وقتی کامپوننت Unmount میشه۲. وقتی dependencyها تغییر میکنن و effect قراره دوباره اجرا بشهReact قبل از اجرای effect جدید، اول cleanup قبلی رو اجرا میکنه.چه چیزایی Cleanup نمیخوان؟setState سادهمحاسبات داخل renderچیزایی که subscription یا async نیستنهر چی resource خارجی یا زماندار باشه → cleanup لازم داره.چرا اینقدر مهمه؟چون اگه رعایت نکنی:❌ memory leak❌ کند شدن اپ❌ مصرف زیاد باتری❌ باگهای عجیب❌ state آپدیت روی کامپوننت حذفشدهولی اگه رعایت کنی:✅ اپ تمیز✅ رفتار قابل پیشبینی✅ performance بهتر✅ کد حرفهایترجمعبندیهر چیزی که داخل useEffect راه میندازی از خودت بپرس:اگه این صفحه بسته شد، باید اینو خاموش کنم یا نه؟اگه جواب &quot;آره&quot; بود → حتماً cleanup بنویس. ممنون که خوندی.</description>
                <category>مهدی فرجی</category>
                <author>مهدی فرجی</author>
                <pubDate>Mon, 23 Feb 2026 17:20:20 +0330</pubDate>
            </item>
                    <item>
                <title>چرا youtube-spotlight-extension رو ساختم؟ (داستان یه دردسر همیشگی تو یوتیوب 😅)</title>
                <link>https://virgool.io/@mehdifaraji/%DA%86%D8%B1%D8%A7-youtube-spotlight-extension-%D8%B1%D9%88-%D8%B3%D8%A7%D8%AE%D8%AA%D9%85-%D8%AF%D8%A7%D8%B3%D8%AA%D8%A7%D9%86-%DB%8C%D9%87-%D8%AF%D8%B1%D8%AF%D8%B3%D8%B1-%D9%87%D9%85%DB%8C%D8%B4%DA%AF%DB%8C-%D8%AA%D9%88-%DB%8C%D9%88%D8%AA%DB%8C%D9%88%D8%A8-%F0%9F%98%85-q6sonaufrr2l</link>
                <description>Youtube spotlight searchهر روز چند ساعت تو یوتیوب می‌گذرونم: ویدیو آموزشی، تحقیق، کامنت‌خونی، پیدا کردن کانال جدید...  ولی هر بار یه چیز اذیتم می‌کرد: پر از نویز و حواس‌پرتی.تبلیغات وسط ویدیو، تبلیغ تو فید، بنرهای تبلیغاتی، ویدیوهای پیشنهادی اسپانسری، عنوان‌ها و تامبنیل‌های بی‌ربط...  می‌خواستم فقط روی همون چیزی که برام مهمه تمرکز کنم، نه اینکه تو دریای محتوا گم بشم.پس تصمیم گرفتم یه ابزار بسازم که بالاخره این مشکل رو حل کنه.مشکل همیشگی: یوتیوب پر از حواس‌پرتیهوقتی یوتیوب باز می‌کنی:- تبلیغ قبل ویدیو، تبلیغ تو فید، تبلیغ کنار پخش‌کننده  - بخش‌های اسپانسری تو ویدیوها  - کلی عنوان و تامبنیل که حواست رو پرت می‌کنه  - پیدا کردن یه کلمه خاص یا کانال مورد علاقه‌ت تو این شلوغی خیلی سخته  ادبلاکرها (مثل uBlock) یه بخشی از تبلیغات رو حذف می‌کنن، ولی هیچ‌کدوم نمی‌تونن محتوای صفحه رو برات فیلتر کنن یا فقط کانال‌های خاص رو برجسته کنن.راه‌حل‌های موجود کافی نبودن- ادبلاکرها → فقط تبلیغ حذف می‌کنن، نه فیلتر محتوا  - افزونه‌های بهبود یوتیوب (Enhancer for YouTube و غیره) → بیشتر کنترل پخش اضافه می‌کنن، نه تمرکز روی محتوا  - اسکریپت‌های userscript → معمولاً با shadow DOM یوتیوب و آپدیت‌های مداومش به مشکل می‌خورن  - خود یوتیوب هم هیچ گزینه‌ای برای spotlight کردن متن یا کانال نداره  چند بار bookmarklet و کدهای دستی زدم، ولی همیشه ناقص بودن و UI خوبی نداشتن.راه‌حل من: youtube-spotlight-extensionیه افزونه سبک کروم ساختم که یه نوار کنترل شیک و متحرک (draggable) روی صفحه یوتیوب اضافه می‌کنه.ویژگی‌های اصلی:- Spotlight متن → هر کلمه‌ای تایپ کنی، بقیه متن‌های صفحه محو می‌شن (opacity ~0.1) → عالی برای جستجوی سریع تو کامنت‌ها، عنوان‌ها یا توضیحات  - Spotlight کانال → با یه کلیک فقط اسم کانال‌ها و @handleها روشن می‌مونن، بقیه صفحه کم‌رنگ می‌شه → خیلی راحت می‌تونی کانال‌های مورد علاقه‌ت رو پیدا کنی  - حذف تبلیغات یک‌کلیکی → اکثر تبلیغات (فید، بنر، ویدیوهای اسپانسری، تبلیغات پخش‌کننده و ...) فوراً حذف می‌شن  - پشتیبانی خودکار از حالت تاریک/روشن یوتیوب  - نوار کنترل draggable روی دسکتاپ، ثابت و وسط‌چین روی موبایل  - بدون نیاز به بیلد — ZIP آماده رو دانلود کن، Load unpacked کن، تموم!همه کارها تو content script انجام می‌شه — سبک، سریع، بدون overhead زیاد.چطور کار می‌کنه؟ (یه نگاه فنی سریع)- از Shadow Root UI (با کمک WXT) برای نوار کنترل استفاده کردم  - MutationObserver برای تشخیص تغییر تم تاریک/روشن  - querySelectorAll + تغییر opacity برای dim کردن عناصر  - لیست کاملی از سلکتورهای تبلیغات (ytd-ad-slot-renderer، .ytp-ad-module و ...) برای حذف  - بدون وابستگی خارجی سنگین — فقط React + WXTکجا ازش استفاده کنی؟تحقیق روی یه موضوع خاص → کلمه کلیدی رو spotlight کن و کامنت‌ها/نتایج رو سریع اسکن کندنبال کردن یه یوتیوبر → حالت کانال رو فعال کن و فقط محتوای اون‌ها رو ببینتماشای بدون تبلیغ → یه کلیک و صفحه تمیز می‌شهمرور سریع → همه چیز رو dim کن جز @منشن‌ها یا عبارت مورد نظرتشروع کاربرو به صفحه Releasesآخرین ZIP رو دانلود کن (پیش‌ساخته، بدون نیاز به npm install)unzip کن → کروم → chrome://extensions/ → Developer mode → Load unpacked → پوشه رو انتخاب کنیوتیوب رو باز کن — نوار spotlight ظاهر می‌شه!ریپو: https://github.com/1mehdifaraji/youtube-spotlight-extensionحرف آخربعد از ساخت و استفاده از این افزونه، واقعاً حس می‌کنم یوتیوب تمیزتر و هدفمندتر شده. دیگه تو نویز گم نمی‌شم و دقیقاً می‌رم سراغ چیزی که می‌خوام.اگه تو هم از یوتیوب زیاد استفاده می‌کنی و از شلوغی خسته شدی، حتماً امتحانش کن.یه ⭐ روی گیت‌هاب خیلی انرژی می‌ده! 😍ممنون که خوندی — بریم یوتیوب رو بهتر کنیم! 🔦📺</description>
                <category>مهدی فرجی</category>
                <author>مهدی فرجی</author>
                <pubDate>Thu, 05 Feb 2026 05:43:23 +0330</pubDate>
            </item>
                    <item>
                <title>چرا zustand-mmkv-storage رو ساختم؟ (داستان یه دردسر React Native 😅)</title>
                <link>https://virgool.io/@mehdifaraji/%DA%86%D8%B1%D8%A7-zustand-mmkv-storage-%D8%B1%D9%88-%D8%B3%D8%A7%D8%AE%D8%AA%D9%85-%D8%AF%D8%A7%D8%B3%D8%AA%D8%A7%D9%86-%DB%8C%D9%87-%D8%AF%D8%B1%D8%AF%D8%B3%D8%B1-react-native-%F0%9F%98%85-cqkeesyous0q</link>
                <description>چند ماه پیش، در حال ساخت یک اپلیکیشن React Native با Zustand برای مدیریت حالت بودم. همه چیز عالی به نظر می‌رسید API ساده، بدون boilerplate زیاد، تجربه توسعه فوق‌العاده.اما وقتی نوبت به ذخیره‌سازی حالت رسید، (مثل توکن لاگین، تنظیمات کاربر، سبد خرید) مشکلات شروع شد.ابتدا از AsyncStorage استفاده کردم. کار می‌کرد... اما کند بود. نوشتن و خواندن داده‌ها گاهی باعث لگ رابط کاربری می‌شد و برای داده‌های حساس هیچ رمزنگاری داخلی نداشت.می‌دانستم MMKV استاندارد طلایی ذخیره‌سازی سریع در React Native است، اما هر بار اتصال دستی آن به persist در Zustand، تکراری و پرخطا بود.پس تصمیم گرفتم یک بار برای همیشه این مشکل را حل کنم و zustand-mmkv-storage متولد شد.مشکل ذخیره‌سازی سنتی در React Nativeدر اپلیکیشن‌های موبایل، ذخیره‌سازی اجباری است:کاربر انتظار دارد تم تاریک/روشن حفظ شودسبد خرید با بستن اپ خالی نشودجلسه لاگین بعد از ری‌استارت گوشی ادامه داشته باشدسال‌ها AsyncStorage راه‌حل پیش‌فرض بود. ساده است و همه‌جا کار می‌کند.اما واقعیت در سال ۲۰۲۵:کاملاً مبتنی بر جاوااسکریپتفقط async که سبب لگ UI میشهبدون رمزنگاریتوسط تیم اصلی React Native منسوخ شدهبنچمارک‌ها نشان می‌دهند MMKV در سناریوهای واقعی ۳۰ تا ۱۰۰ برابر سریع‌تر از AsyncStorage است.کشف MMKV: تغییر دهنده بازی برای عملکردMMKV (توسعه‌یافته توسط Tencent و اکنون توسط Marc Rousavy نگهداری می‌شود) یک ذخیره‌ساز key-value مدرن است که برای سرعت ساخته شده:هسته ++C با binding های JSIخواندن همزمان (بدون await)رمزنگاری داخلیایمن برای multi-process، حجم کم، سازگار با Expoدر اپ‌های بزرگ تولید استفاده می‌شود و توصیه پیش‌فرض برای ذخیره‌سازی با عملکرد بالا شده است.Middleware Persist در Zustandpersist در Zustand فوق‌العاده است فقط store را wrap کنید و سریالایزیشن/هیدراته شدن را مدیریت می‌کند.اما نیاز به یک backend ذخیره‌سازی خوب دارد. اتصال دستی MMKV یعنی:کد تکراریعدم اشتراک‌گذاری هوشمند instanceباگ‌های آسان در هیدراته شدنراه‌حل من: zustand-mmkv-storagezustand-mmkv-storage را ساختم یک آداپتور کوچک (&lt;1KB) که MMKV را مستقیماً با Zustand هماهنگ می‌کند.ویژگی‌هایی که روی آن‌ها تمرکز کردم:Lazy dynamic importGlobal instance cachingپشتیبانی کامل از رمزنگاریچندین store ایزولهبهترین شیوه‌های هیدراته شدن داخلیسازگار با Expo و React Nativeشروع کارnpm install zustand-mmkv-storage zustand react-native-mmkvمثال سادهimport { create } from &#039;zustand&#039;;
import { persist, createJSONStorage } from &#039;zustand/middleware&#039;;
import { mmkvStorage } from &#039;zustand-mmkv-storage&#039;;

export const useCounterStore = create(
  persist(
    (set) =&gt; ({
      count: 0,
      increment: () =&gt; set((state) =&gt; ({ count: state.count + 1 })),
    }),
    {
      name: &#039;counter&#039;,
      storage: createJSONStorage(() =&gt; mmkvStorage),
    }
  )
);مثال پیشرفته: توکن لاگین رمزنگاری‌شدهimport { createMMKVStorage } from &#039;zustand-mmkv-storage&#039;;

const encryptedStorage = createMMKVStorage({
  id: &#039;auth-vault&#039;,
  encryptionKey: &#039;secret&#039;,
});

export const useAuthStore = create(
  persist(
    (set) =&gt; ({
      token: &#039;&#039;,
      setToken: (token) =&gt; set({ token }),
    }),
    {
      name: &#039;auth&#039;,
      storage: createJSONStorage(() =&gt; encryptedStorage),
    }
  )
);حالا توکنت روی دیسک رمزنگاری شده و کاملاً جدا از بقیه داده‌هاست.کجا ازش استفاده کنی؟توکن احراز هویتتنظیمات اپ (تم، زبان)سبد خرید آفلاینوضعیت onboardingفیچر فلگ‌هاحرف آخربعد از سوئیچ به zustand-mmkv-storage اپ‌هام واقعاً سریع‌تر شدن دیگه منتظر ذخیره‌سازی نیستم.اگه تو هم Zustand تو React Native استفاده می‌کنی، حتماً امتحانش کن:npm i zustand-mmkv-storageگیت‌هاب - npmخوشت اومد، یه ⭐ روی گیت‌هاب بزن کلی انرژی می‌ده! 😍ممنون که خوندی بریم React Native رو سریع‌تر کنیم🐻✨#react-native #open-source #zustand #mobile-development #mmkv #react-native-storage #persist</description>
                <category>مهدی فرجی</category>
                <author>مهدی فرجی</author>
                <pubDate>Tue, 30 Dec 2025 08:40:45 +0330</pubDate>
            </item>
                    <item>
                <title>مرج و باندل کردن فایل های سواگر swagger</title>
                <link>https://virgool.io/@mehdifaraji/%D9%85%D8%B1%D8%AC-%D9%88-%D8%A8%D8%A7%D9%86%D8%AF%D9%84-%DA%A9%D8%B1%D8%AF%D9%86-%D9%81%D8%A7%DB%8C%D9%84-%D9%87%D8%A7%DB%8C-%D8%B3%D9%88%D8%A7%DA%AF%D8%B1-swagger-zbscwjonkrao</link>
                <description>open-api-swaggerتاحالا سعی کردی واسه api با swagger مستند سازی کنی؟روشی که بعد تماشای چندتا آموزش‌ و خوندن مستندات رسمی OPENAPI انجام میدی، کدنویسی تو یه فایل yaml و تعریف اجزا، ریسورس و غیره همه تو همون فایله.در نهایت صدها خط کد تو یه فایل داری که نه خوندنش راحته و پس از مدتی تغییر یا دیباگ کردنش کار ساده ای نیس.و تنها راهی که کار و تجربه توسعه رو راحت میکنه، استفاده از SwaggerHub هستش که رایگان نیست.این روش زیاد اکی نیس، مگه نه؟یه راه بهتری هست اونم اینه که پوشه‌ و فایل‌های yaml خوب نامگذاری بشن و اکسپورت بشن به یه فایل اصلی yaml.اساساً ما همه چی رو به عنوان یک ماژول در نظر می گیریم و اکسپورت می کنیم که بعدش بتونیم ایمپورت کنیم تو یه فایل اصلی.درواقع همه فایل‌هارو باندل می‌کنیم و یک فایل yaml به عنوان داکیونمت سواگر خواهیم داشت.ساختاری که من پیشنهاد می کنم، ایجاد پوشه های جداگونه برای پارامتر ها، ریسورس ها، رسپانس ها و آدرس های api هستش.در نهایت میشه همه ی فایل های yaml رو با این دستور مرج کنیم:npx swagger-cli bundle src/openapi.yaml — outfile _build/openapi.yaml — type yamlابزار swagger-cli همه فایل‌های yaml رو تو یه فایل مرج میکنه که تو پوشه build_ میزارتش.برای دیدن نتیجه کار، می تونی فایل بیلد شده رو کپی کنی و تو ادیتور Swagger پیست کنی.سورس کد کامل رو هم گذاشتم تو گیتهابم میتونی ببینی.</description>
                <category>مهدی فرجی</category>
                <author>مهدی فرجی</author>
                <pubDate>Sun, 01 Sep 2024 14:04:25 +0330</pubDate>
            </item>
            </channel>
</rss>