نظرات مطالب
خواندن اطلاعات از فایل اکسل با استفاده از LinqToExcel
همه چیز رو مطابق مستندات انجام دادم ولی خطای زیر رخ میده
Additional information: Could not load file or assembly 'log4net, Version=1.2.13.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a' or one of its dependencies. The located assembly's manifest definition does not  
نظرات مطالب
توزیع پروژه‌های ASP.NET MVC بدون ارائه فایل‌های View آن
تمام نکاتی را که فرمودید اعمال کردم ولی با این حال هلپر‌های موجود در App_Code قابل شناسایی نبودند و برای فضای نام MVC5.Web.App_Code  خطای یافت نشد اعلام میکرد . 
هلپر‌ها را به فولدر دیگری انتقال دادم ولی بازم با وجود فضای نام مربوطه ، خطای 
 'ToastrBuilder' does not exist in the current context
نشان داده میشود .

نظرات مطالب
دسترسی به Collectionها در یک ترد دیگر در WPF
چرا _syncLock به صورت static تعریف شده؟
به نظرم static بودن نه تنها غیرضروریه، بلکه حتی در مواردی (هر چند نادر) که چند نمونه‌ی جداگانه از Persons در بخش‌های مختلف برنامه همزمان در حال کار باشند، باید همه شون منتظر آزاد شدن شیء syncLock برای انجام عملیات خودشون بشن که در حقیقت نیازی نیست.
نظرات مطالب
ASP.NET MVC #5
در مورد فعال کردن MvcBuildViews  من وقتی این کارو انجام میدم با خطای زیر روبه رو می‌شم.
Error1The type or namespace name 'Optimization' does not exist in the namespace 'System.Web' (are you missing an assembly reference?)c:\Windows\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files\temp\93647db0\578ae027\App_Web_krwrdmvd.0.cs26
 
نظرات مطالب
متدهای کمکی مفید در پروژه های asp.net mvc
خطای زیر را میدهد:
LINQ to Entities does not recognize the method 'Persia.SolarDate ConvertToPersian(System.DateTime)' method, and this method cannot be translated into a store expression.  
برای ساخت مدل
من اول به روش دیتابیس فرست  EDMX را ساختم و سپس از ADO.NET DbContext Generator  استفاده کردم.

اشتراک‌ها
قدرت برنامه نویسی تنبل طور

Here are 13 techniques and tools that prove the power of lazy programming. The next time the boss tells you it’s time to roll up your sleeves and lean into the console, head to the nap room instead. 

قدرت برنامه نویسی تنبل طور
مطالب
کوئری نویسی در EF Core - قسمت پنجم - اعمال تجمعی - بخش اول
امکان انجام محاسبات تجمعی، یکی از مواردی است که قدرت بانک‌های اطلاعاتی رابطه‌ای را نمایش می‌دهد. توسط این نوع کوئری‌ها از محدوده‌ی CRUD (ثبت/ویرایش/به روز رسانی) خارج شده و وارد دنیای تصمیم‌گیری‌ها می‌شویم. تعداد مثال‌های اعمال تجمعی این سری قابل توجه‌است. به همین جهت در دو قسمت ارائه می‌شوند.


مثال 1: چه تعداد امکانات، توسط این مجموعه ارائه می‌شود؟
var count = context.Facilities.Count();
برای شمارش ساده‌ی تعداد ردیف‌های یک کوئری، از متد Count استفاده می‌شود که به صورت زیر ترجمه خواهد شد:



مثال 2: چه تعداد امکانات گران قیمتی توسط این مجموعه ارائه می‌شود؟
می‌خواهیم تعداد امکاناتی را بیابیم که guestcost آن‌ها بزرگتر یا مساوی 10 است.
var count = context.Facilities.Count(x => x.GuestCost >= 10);
این گزارش نسبت به مثال قبلی، یک Where را بیشتر دارد که می‌توان این شرط را در همان متد Count نیز ذکر کرد؛ با این خروجی نهایی:



مثال 3: هر کاربر چه تعدادی کاربر دیگری را توصیه کرده‌است؟
خروجی این گزارش بر اساس recommendedby و count باشد و مرتب شده‌ی بر اساس ID افراد.
var members = context.Members
                        .Where(member => member.RecommendedBy != null)
                        .GroupBy(member => member.RecommendedBy)
                        .Select(group => new
                        {
                            RecommendedBy = group.Key,
                            Count = group.Count()
                        })
                        .OrderBy(result => result.RecommendedBy)
                        .ToList();
در دو مثال قبل، تنها یک حاصل عددی را از گزارشات دریافت کردیم. اما در اینجا نیاز است به ازای هر شخص، تعداد توصیه شده‌های او محاسبه شوند. به همین جهت نیاز است اطلاعات را به ازای هر شخص مجزا، گروه بندی کرد و سپس متد Count را بر روی این گروه ویژه اعمال نمود و همینطور برای مابقی گروه‌ها. در اینجا GroupBy بر روی خاصیت RecommendedBy انجام شده‌است. این خاصیت در Select بعدی، همان group.Key استفاده شده‌است.



مثال 4: تعداد slots رزرو شد‌ه‌ی به ازای هر کدام از امکانات موجود را نمایش دهید.
جهت یادآوری از قسمت اول: «هر رزرو کردن مکان و امکاناتی در این مجموعه، «نیم ساعته» است. بنابراین Slots در اینجا به معنای تعداد نیم ساعت‌های رزرو کردن یک مکان خاص است؛ که به آن «half hour slots» نیز گفته می‌شود و زمان شروع این رزرو نیز ثبت می‌شود.»
خروجی این گزارش بر اساس facid و Total Slots باشد و مرتب شده‌ی بر اساس ID هر امکان موجود.
var facilities = context.Bookings
                                    .GroupBy(booking => booking.FacId)
                                    .Select(group => new
                                    {
                                        FacId = group.Key,
                                        TotalSlots = group.Sum(booking => booking.Slots)
                                    })
                                    .OrderBy(result => result.FacId)
                                    .ToList();
در این گزارش بجای استفاده از متد Count، از متد Sum استفاده شده‌است. چون می‌خواهیم جمع slots را به ازای هر امکان موجود محاسبه کنیم، ابتدا گروه‌های مجزای این امکانات را تشکیل می‌دهیم و سپس Sum را به هر گروهی، به صورت مجزایی اعمال می‌کنیم. در اینجا group.Key دقیقا همان booking.FacId است و در متد Sum، امکان دسترسی به خواص booking نیز وجود دارد.



مثال 5: تعداد slots رزرو شد‌ه‌ی در ماه September 2012 را به ازای هر کدام از امکانات موجود، نمایش دهید.
خروجی این گزارش بر اساس facid و Total Slots باشد و مرتب شده‌ی بر اساس ID تعداد slots.
var date1 = new DateTime(2012, 09, 01);
var date2 = new DateTime(2012, 10, 01);

var facilities = context.Bookings
                                    .Where(booking => booking.StartTime >= date1
                                                        && booking.StartTime < date2)
                                    .GroupBy(booking => booking.FacId)
                                    .Select(group => new
                                    {
                                        FacId = group.Key,
                                        TotalSlots = group.Sum(booking => booking.Slots)
                                    })
                                    .OrderBy(result => result.TotalSlots)
                                    .ToList();
این گزارش تنها یک قسمت Where را نسبت به گزارش قبلی بیشتر دارد و این Where باید دقیقا پیش‌از گروه بندی اطلاعات اعمال شود. به عبارتی ابتدا باید ردیف‌های اصلی گزارش مشخص باشند تا بتوان آن‌ها را گروه بندی کرد.



مثال 6: محاسبه کنید در سال 2012 و به ازای هر ماه مجزای آن، چه تعداد slots رزرو شده‌اند.
خروجی این گزارش بر اساس facid, month, Total Slots باشد و مرتب شده‌ی بر اساس ID و شماره‌ی ماه.
var date1 = new DateTime(2012, 01, 01);
var date2 = new DateTime(2013, 01, 01);

var facilities = context.Bookings
                                    .Where(booking => booking.StartTime >= date1
                                                        && booking.StartTime < date2)
                                    .GroupBy(booking => new { booking.FacId, booking.StartTime.Month })
                                    .Select(group => new
                                    {
                                        group.Key.FacId,
                                        group.Key.Month,
                                        TotalSlots = group.Sum(booking => booking.Slots)
                                    })
                                    .OrderBy(result => result.FacId)
                                        .ThenBy(result => result.Month)
                                    .ToList();
دو نکته‌ی جدید در این گزارش نسبت به مثال‌های قبلی وجود دارند:
الف) می‌توان گروه بندی را بر روی بیش از یک ستون اعمال کرد. در این حالت در Select بعدی، group.Key به کل شیء گروه بندی شده‌، اشاره می‌کند.
ب) روش انتخاب ماه میلادی از یک خاصیت DateTime و گروه بندی بر اساس آن

که به صورت زیر ترجمه می‌شود:



مثال 7: چه تعداد کاربر مجموعه، حداقل یکبار امکاناتی را رزرو کرده‌اند؟

var count = context.Bookings.Select(booking => booking.MemId).Distinct().Count();
هدف از این گزارش، رسیدن به COUNT DISTINCT است.
- (*)COUNT یعنی بازگشت تعداد ردیف‌های نهایی گزارش.
- COUNT(address) یعنی بازگشت تعداد آدرس‌های غیرنال، در کل ردیف‌های نهایی گزارش.
- COUNT(DISTINCT address) یعنی بازگشت تعداد آدرس‌های غیرمشابه در کل ردیف‌های نهایی گزارش.

  COUNT DISTINCT را EF-Core به صورت ترکیبی از یک sub-query ترجمه می‌کند:



مثال 8: امکاناتی را لیست کنید که بیش از 1000 slots رزرو شده دارند.
خروجی این گزارش بر اساس facid و Total Slots باشد و مرتب شده‌ی بر اساس ID هر امکان موجود.
var facilities = context.Bookings
                                    .GroupBy(booking => booking.FacId)
                                    .Select(group => new
                                    {
                                        FacId = group.Key,
                                        TotalSlots = group.Sum(booking => booking.Slots)
                                    })
                                    .Where(result => result.TotalSlots > 1000)
                                    .OrderBy(result => result.FacId)
                                    .ToList();
در مثال‌های قبل، از Where جهت تشکیل تعداد ردیف‌های اصلی گزارش و سپس گروه بندی آن‌ها استفاده کردیم. در اینجا می‌خواهیم Where را بر روی نتیجه‌ی حاصل از گروه بندی اعمال کنیم. در کوئری LINQ فوق، خواص قابل دسترسی پس از Select نهایی، همان‌هایی هستند که توسط آن ارائه می‌شوند. این نوع Whereها در SQL حاصل به Having ترجمه خواهند شد:



مثال 9: میزان فروش کل هر امکان موجود را محاسبه کنید.
خروجی این گزارش بر اساس name, revenue باشد و مرتب شده‌ی بر اساس میزان فروش. بخاطر داشته باشید که میزان فروش کاربران ثبت نام شده با کاربران مهمان یکی نیست.
 var facilities =
                            context.Bookings.Select(booking =>
                                new
                                {
                                    booking.Facility.Name,
                                    Revenue = booking.MemId == 0 ?
                                            booking.Slots * booking.Facility.GuestCost
                                            : booking.Slots * booking.Facility.MemberCost
                                })
                                .GroupBy(b => b.Name)
                                .Select(group => new
                                {
                                    Name = group.Key,
                                    TotalRevenue = group.Sum(b => b.Revenue)
                                })
                                .OrderBy(result => result.TotalRevenue)
                                .ToList();
همانند تمام گروه بندی‌ها، ابتدا باید ردیف‌های اصلی گزارش را تشکیل داد و سپس بر روی آن‌ها گروه بندی نهایی را اعمال نمود. به همین جهت در ابتدا خاصیت محاسباتی Revenue را بر اساس کاربران مهمان با ID مساوی صفر و کاربران اصلی مجموعه، تشکیل داده و گروه بندی را به نام هر مجموعه اعمال می‌کنیم. سپس جمع Revenue محاسبه شده را به ازای هر گروه محاسبه کرده و نتیجه را بازگشت می‌دهیم.



مثال 10: کدامیک از امکانات موجود، میزان فروشی کمتر از 1000 داشته‌اند؟
خروجی این گزارش بر اساس name, revenue باشد و مرتب شده‌ی بر اساس میزان فروش. بخاطر داشته باشید که میزان فروش کاربران ثبت نام شده با کاربران مهمان یکی نیست.
var facilities =
                            context.Bookings.Select(booking =>
                                new
                                {
                                    booking.Facility.Name,
                                    Revenue = booking.MemId == 0 ?
                                            booking.Slots * booking.Facility.GuestCost
                                            : booking.Slots * booking.Facility.MemberCost
                                })
                                .GroupBy(b => b.Name)
                                .Select(group => new
                                {
                                    Name = group.Key,
                                    TotalRevenue = group.Sum(b => b.Revenue)
                                })
                                .Where(result => result.TotalRevenue < 1000)
                                .OrderBy(result => result.TotalRevenue)
                                .ToList();
این مورد نیز همانند گزارش 9 است که یک Where به نتیجه‌ی حاصل از آن اعمال شده که در خروجی نهایی به Having ترجمه می‌شود:



مثال 11: کدامیک از امکانات موجود، بیشترین slots رزرو شده را دارد؟

var item = context.Bookings
                                    .GroupBy(booking => booking.FacId)
                                    .Select(group => new
                                    {
                                        FacId = group.Key,
                                        TotalSlots = group.Sum(booking => booking.Slots)
                                    })
                                    .OrderByDescending(result => result.TotalSlots)
                                    .FirstOrDefault();
ساده‌ترین روش حل این مساله، گروه بندی اطلاعات بر اساس هر امکان موجود و سپس محاسبه‌ی TotalSlots هرکدام، به صورت مجزایی است. در ادامه ردیف‌های حاصل را بر اساس TotalSlots محاسبه شده، به صورت نزولی مرتب می‌کنیم. اولین ردیفی که در بالای گزارش قرار می‌گیرد همان FacId ای است که بیشترین TotalSlots را دارد.



کدهای کامل این قسمت را در اینجا می‌توانید مشاهده کنید.
اشتراک‌ها
کتابخانه lobipanel

jQuery plugin for bootstrap panels. It extends panels with several common and useful functions.  Demo

Features

  • Sort, drag, expand, resize, minimize bootstrap panels
  • Specify url and load content in panel from this url
  • Change the name of the panel
  • Customize action icons and action tooltips
  • Works for nested panels
  • HTML5 localStorage support
    • Save panel state: (pinned, unpinned, collapsed, fullscreen, minimized) and apply it on page load
    • Save panel position among siblings and apply on next time 
کتابخانه lobipanel
نظرات مطالب
استفاده از jQuery یا‌ MS Ajax control toolkit
سلام
من مطلب جالبی از نویسنده کتاب Asp.net MVC خواندم که طبق تحقیقات انجام شده توسط ماکروسافت است:
اونها به Asp.net عبارت Bloated را دادند و این به این معنی است:

The term bloated is often thrown at frameworks or technologies that either:

People don’t understand or aren’t completely aware of and don’t want to take the time to learn

Come from a provider people don’t respect or like

People have heard bad things about

Are actually bloated

و این نظریه که اغلب مردم نسبت به Asp.net Ajax دارند.
مطالب
مدیریت پیشرفته‌ی حالت در React با Redux و Mobx - قسمت هشتم - تنظیمات پروژه‌های React برای کار با Mobx decorators
تا اینجا دو مثالی را که از Mobx بررسی کردیم (مثال ورود متن و مثال کامپوننت شمارشگر)، به عمد به همراه decoratorهای @ دار آن نبودند. برای مثال در قسمت قبل، یک کلاس را با یک خاصیت ایجاد کردیم که روش مزین سازی خاصیت value آن را با  observable decorator، توسط متد decorate انجام دادیم و این هم یک روش کار با MobX است؛ بدون اینکه نیاز به تنظیمات خاصی را داشته باشد:
import { decorate } from "mobx";

class Count {
  value = 0;
}

decorate(Count, { value: observable });
const count = new Count();
اما اگر همان مثال بسیار ساده‌ی ورود متن را بخواهیم توسط decoratorهای @ دار MobX پیاده سازی کنیم ... پروژه‌ی استاندارد React ما کامپایل نخواهد شد که در این قسمت، روش رفع این مشکل را بررسی می‌کنیم.


بازنویسی مثال ورود متن و نمایش آن با Mobx decorators

در اینجا یک text-box، به همراه دو div در صفحه رندر خواهند شد که قرار است با ورود اطلاعاتی در text-box، یکی از آن‌ها (text-display) این اطلاعات را به صورت معمولی و دیگری (text-display-uppercase) آن‌را به صورت uppercase نمایش دهد. روش کار انجام شده هم مستقل از React است و به صورت مستقیم با استفاده از DOM API عمل شده‌است. این مثال را پیشتر در اولین قسمت بررسی MobX، ملاحظه کردید. اکنون اگر بخواهیم بجای شیءای که توسط متد observable کتابخانه‌ی MobX محصور شده‌است:
const text = observable({
  value: "Hello world!",
  get uppercase() {
    return this.value.toUpperCase();
  }
});
از یک کلاس ES6 به همراه Mobx decorators استفاده کنیم، به یک چنین پروژه‌ی جدیدی خواهیم رسید:
ابتدا یک پروژه‌ی جدید React را ایجاد می‌کنیم:
> create-react-app state-management-with-mobx-part3
> cd state-management-with-mobx-part3
> npm start
در ادامه کتابخانه‌ی mobx را نیز نصب می‌کنیم. برای این منظور پس از باز کردن پوشه‌ی اصلی برنامه توسط VSCode، دکمه‌های ctrl+` را فشرده (ctrl+back-tick) و دستور زیر را در ترمینال ظاهر شده وارد کنید:
> npm install --save mobx
در ادامه، ابتدا فایل public\index.html را جهت نمایش دو div و یک text-box، ویرایش می‌کنیم:
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>MobX Basics, part 3</title>
    <meta charset="UTF-8" />
    <link href="src/styles.css" />
  </head>

  <body>
    <main>
      <input id="text-input" />
      <p id="text-display"></p>
      <p id="text-display-uppercase"></p>
    </main>

    <script src="src/index.js"></script>
  </body>
</html>
سپس محتویات فایل src\index.js را نیز به نحو زیر تغییر می‌دهیم:
import { autorun, computed, observable } from "mobx";

const input = document.getElementById("text-input");
const textDisplay = document.getElementById("text-display");
const loudDisplay = document.getElementById("text-display-uppercase");

class Text {
  @observable value = "Hello World";
  @computed get uppercase() {
    return this.value.toUpperCase();
  }
}

const text = new Text();

input.addEventListener("keyup", event => {
  text.value = event.target.value;
});

autorun(() => {
  input.value = text.value;
  textDisplay.textContent = text.value;
  loudDisplay.textContent = text.uppercase;
});
تنها تفاوت این نگارش با نگارش قبلی آن، استفاده از کلاس Text که یک کلاس ES6 به همراه MobX Decorators است، بجای یک شیء ساده‌ی جاوا اسکریپتی می‌باشد. در اینجا خاصیت value به صورت observable@ تعریف شده و در نتیجه‌ی تغییر مقدار آن در کدهای برنامه، خاصیت محاسباتی وابسته‌ی به آن یا همان uppercase که با computed@ تزئین شده، به صورت خودکار به روز رسانی خواهد شد. متد autorun نیز به این تغییرات که حاصل فشرده شدن کلیدها هستند، واکنش نشان داده و متن دو div موجود در صفحه را به روز رسانی می‌کند.

اکنون اگر در همین حال، برنامه را با دستور npm start اجرا کنیم، با خطای زیر متوقف خواهیم شد:
./src/index.js
SyntaxError: \src\index.js: Support for the experimental syntax 'decorators-legacy' isn't currently enabled (8:3):

   6 | 
   7 | class Text {
>  8 |   @observable value = "Hello World";
     |   ^
   9 |   @computed get uppercase() {
  10 |     return this.value.toUpperCase();
  11 |   }

راه حل اول: از Decorators استفاده نکنیم!

یک راه حل مشکل فوق این است که بدون هیچ تغییری در ساختار پروژه‌ی React خود، اصلا از decorator syntax استفاده نکنیم. برای مثال اگر یک کلاس متداول MobX ای چنین شکلی را دارد:
import { observable, computed, action } from "mobx";

class Timer {
  @observable start = Date.now();
  @observable current = Date.now();

  @computed
  get elapsedTime() {
    return this.current - this.start + "milliseconds";
  }

  @action
  tick() {
    this.current = Date.now();
  }
}
می‌توان آن‌را بدون استفاده از decorator syntax، به صورت زیر نیز تعریف کرد:
import { observable, computed, action, decorate } from "mobx";

class Timer {
  start = Date.now();
  current = Date.now();

  get elapsedTime() {
    return this.current - this.start + "milliseconds";
  }

  tick() {
    this.current = Date.now();
  }
}

decorate(Timer, {
  start: observable,
  current: observable,
  elapsedTime: computed,
  tick: action
});
نمونه‌ی این روش را در قسمت قبل با تعریف شیء شمارشگر مشاهده کرده‌اید. در اینجا با توجه به اینکه Decorators در جاوا اسکریپت چیزی نیستند بجز بیان زیبای higher-order functions و higher-order functions هم توابعی هستند که توابع دیگر را با ارائه‌ی قابلیت‌های بیشتری، محصور می‌کنند، به همین جهت هر کاری را که بتوان با تزئین کننده‌ها انجام داد، همان را با توابع معمولی جاوا اسکریپتی نیز می‌توان انجام داد. اینکار را در مثال فوق توسط متد decorate مشاهده می‌کنید. این متد ابتدا نوع کلاس خاصی را دریافت کرده و سپس در پارامتر دوم آن می‌توان شیءای را تعریف کرد که خواص آن، همان خواص کلاس پارامتر اول است و مقادیر این خواص، تزئین کننده‌هایی هستند که قرار است برای آن‌ها بکار گرفته شوند. مزیت این روش بدون تغییر باقی ماندن تعریف کلاس Timer در اینجا و همچنین انجام هیچگونه تغییری در ساختار پروژه‌ی React، بدون نیاز به نصب بسته‌های کمکی اضافی است.

همچنین در این حالت بجای استفاده از کامپوننت‌های کلاسی، باید از روش بکارگیری متد observer برای محصور کردن کامپوننت تابعی تعریف شده استفاده کرد (تا دیگر نیازی به ذکر observer class@ نباشد):
const Counter = observer(({ count }) => {
  return (
   // ...
  );
});


راه حل دوم: از تایپ‌اسکریپت استفاده کنید!

create-react-app امکان ایجاد پروژه‌های React تایپ‌اسکریپتی را با ذکر سوئیچ typescript نیز دارد:
> create-react-app my-proj1 --typescript
پس از ایجاد پروژه، فایل tsconfig.json آن‌را یافته و experimentalDecorators آن‌را به true تنظیم کنید:
{
  "compilerOptions": {
    // ...
    "experimentalDecorators": true
  }
}
این تنها تغییری است که مورد نیاز می‌باشد و پس از آن برنامه‌ی React جاری، بدون مشکلی می‌تواند با decorators کار کند.


فعالسازی MobX Decorators در پروژه‌های استاندارد React مبتنی بر ES6

MobX از legacy" decorators spec" پشتیبانی می‌کند. یعنی اگر پروژه‌ای از spec جدید استفاده کند، دیگر نخواهد توانست با MobX فعلی کار کند. این هم مشکل MobX نیست. مشکل اینجا است که باید دانست کلا decorators در زبان جاوااسکریپت هنوز در مرحله‌ی آزمایشی قرار دارند و تکلیف spec نهایی و تائید شده‌ی آن مشخص نیست.
برای فعالسازی decorators در یک پروژه‌ی React استاندارد مبتنی بر ES6، شاید کمی جستجو کنید و به نتایجی مانند افزودن فایل babelrc. به ریشه‌ی پروژه و نصب افزونه‌هایی مانند babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties@ برسید. اما ... این‌ها بدون اجرای دستور npm run eject کار نمی‌کنند و اگر این دستور را اجرا کنیم، در نهایت به یک فایل package.json بسیار شلوغ خواهیم رسید (اینبار ارجاعات به Babel، Webpack و تمام ابزارهای دیگر نیز ظاهر می‌شوند). همچنین این عملیات نیز یک طرفه‌است. یعنی از این پس قرار است کنترل تمام این پشت صحنه، در اختیار ما باشد و به روز رسانی‌های بعدی create-react-app را با مشکل مواجه می‌کند. این گزینه صرفا مختص توسعه دهندگان پیشرفته‌ی React است. به همین جهت نیاز به روشی را داریم تا بتوانیم تنظیمات Webpack و کامپایلر Babel را بدون اجرای دستور npm run eject، تغییر دهیم تا در نتیجه، decorators را در آن فعال کنیم و خوشبختانه پروژه‌ی react-app-rewired دقیقا برای همین منظور طراحی شده‌است.

بنابراین ابتدا بسته‌های زیر را نصب می‌کنیم:
> npm i --save-dev customize-cra react-app-rewired
بسته‌ی react-app-rewired، امکان بازنویسی تنظیمات webpack پروژه‌ی react را بدون eject آن میسر می‌کند. customize-cra نیز با استفاده از امکانات همین بسته، نگارش‌های جدیدتر create-react-app را پشتیبانی می‌کند.

پس از نصب این پیشنیازها، فایل جدید config-overrides.js را به ریشه‌ی پروژه، جائیکه فایل package.json قرار گرفته‌است، با محتوای زیر اضافه کنید تا پشتیبانی ازlegacy" decorators spec" فعال شوند:
const {
  override,
  addDecoratorsLegacy,
  disableEsLint
} = require("customize-cra");

module.exports = override(
  // enable legacy decorators babel plugin
  addDecoratorsLegacy(),

  // disable eslint in webpack
  disableEsLint()
);
در ادامه فایل package.json را گشوده و قسمت scripts آن‌را برای استفاده‌ی از react-app-rewired، به صورت زیر بازنویسی کنید تا امکان تغییر تنظیمات webpack به صورت پویا در زمان اجرای برنامه، میسر شود:
  "scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test",
    "eject": "react-app-rewired eject"
  },
پس از این تغییرات، نیاز است دستور npm start را یکبار دیگر از ابتدا اجرا کنید. اکنون برنامه بدون مشکل کامپایل شده و خروجی بدون خطایی در مرورگر نمایش داده خواهد شد.


تنظیمات ESLint مخصوص کار با decorators

فایل ویژه‌ی eslintrc.json. که در ریشه‌ی پروژه قرار می‌گیرد (این فایل بدون نام است و فقط از پسوند تشکیل شده)، برای پروژه‌های MobX، باید حداقل تنظیم زیر را داشته باشد تا ESLint بتواند legacyDecorators را نیز پردازش کند:
{
  "extends": "react-app",
  "parserOptions": {
    "ecmaFeatures": {
      "legacyDecorators": true
    }
  }
}
و یا یک نمونه‌ی غنی شده‌ی فایل eslintrc.json. مخصوص برنامه‌های React به صورت زیر است:
{
    "env": {
        "node": true,
        "commonjs": true,
        "browser": true,
        "es6": true,
        "mocha": true
    },
    "settings": {
        "react": {
            "version": "detect"
        }
    },
    "parserOptions": {
        "ecmaFeatures": {
            "jsx": true,
            "legacyDecorators": true
        },
        "ecmaVersion": 2018,
        "sourceType": "module"
    },
    "plugins": [
        "babel",
        "react",
        "react-hooks",
        "react-redux",
        "no-async-without-await",
        "css-modules",
        "filenames",
        "simple-import-sort"
    ],
    "rules": {
        "no-const-assign": "warn",
        "no-this-before-super": "warn",
        "constructor-super": "warn",
        "strict": [
            "error",
            "safe"
        ],
        "no-debugger": "error",
        "brace-style": [
            "error",
            "1tbs",
            {
                "allowSingleLine": true
            }
        ],
        "no-trailing-spaces": "error",
        "keyword-spacing": "error",
        "space-before-function-paren": [
            "error",
            "never"
        ],
        "spaced-comment": [
            "error",
            "always"
        ],
        "vars-on-top": "error",
        "no-undef": "error",
        "no-undefined": "warn",
        "comma-dangle": [
            "error",
            "never"
        ],
        "quotes": [
            "error",
            "double"
        ],
        "semi": [
            "error",
            "always"
        ],
        "guard-for-in": "error",
        "no-eval": "error",
        "no-with": "error",
        "valid-typeof": "error",
        "no-unused-vars": "error",
        "no-continue": "warn",
        "no-extra-semi": "warn",
        "no-unreachable": "warn",
        "no-unused-expressions": "warn",
        "max-len": [
            "warn",
            80,
            4
        ],
        "react/prefer-es6-class": "warn",
        "react/jsx-boolean-value": "warn",
        "react-hooks/rules-of-hooks": "error",
        "react-hooks/exhaustive-deps": "warn",
        "react/prop-types": "off",
        "react-redux/mapDispatchToProps-returns-object": "off",
        "react-redux/prefer-separate-component-file": "off",
        "no-async-without-await/no-async-without-await": "warn",
        "css-modules/no-undef-class": "off",
        "filenames/match-regex": [
            "off",
            "^[a-zA-Z]+\\.*\\b(typescript|module|locale|validate|test|action|api|reducer|saga)?\\b$",
            true
        ],
        "filenames/match-exported": "off",
        "filenames/no-index": "off",
        "simple-import-sort/sort": "error"
    },
    "extends": [
        "react-app",
        "eslint:recommended",
        "plugin:react/recommended",
        "plugin:react-redux/recommended",
        "plugin:css-modules/recommended"
    ],
    "globals": {
        "Atomics": "readonly",
        "SharedArrayBuffer": "readonly",
        "process": true
    }
}
البته برای اینکه این تنظیمات کار کند، باید افزونه‌های زیر را نیز به صورت محلی در ریشه‌ی پروژه‌ی جاری نصب کنید (این مورد از ESLint 6x به بعد اجباری است و از بسته‌های global استفاده نمی‌کند):
>npm i --save-dev eslint babel-eslint eslint-config-react-app eslint-loader eslint-plugin-babel eslint-plugin-react eslint-plugin-css-modules eslint-plugin-filenames eslint-plugin-flowtype eslint-plugin-import eslint-plugin-no-async-without-await eslint-plugin-react-hooks eslint-plugin-react-redux eslint-plugin-redux-saga eslint-plugin-simple-import-sort eslint-loader typescript
پس از آن می‌توان فایل config-overrides.js را به صورت زیر نیز بر اساس تنظیمات فوق، بهبود بخشید:
const {
  override,
  addDecoratorsLegacy,
  useEslintRc
} = require("customize-cra");

module.exports = override(
  addDecoratorsLegacy(),
  useEslintRc(".eslintrc.json")
);


رفع اخطار مرتبط با decorators در VSCode

تا اینجا کار تنظیم کامپایلر babel، جهت پردازش decorators انجام شد. اما خود VSCode نیز چنین اخطاری را در پروژه‌هایی که از decorates استفاده می‌کنند، نمایش می‌دهد:
Experimental support for decorators is a feature that is subject to change in a future release.
Set the 'experimentalDecorators' option in your 'tsconfig' or 'jsconfig' to remove this warning.ts(1219)
برای رفع آن، فایل جدید tsconfig.json را در ریشه‌ی پروژه ایجاد کرده و آن‌را به صورت زیر تکمیل کنید تا ادیتور تایپ‌اسکریپتی VSCode، دیگر خطاهای مرتبط با decorators را نمایش ندهد:
{
    "compilerOptions": {
        "experimentalDecorators": true,
        "allowJs": true
    }
}

کدهای کامل این قسمت را می‌توانید از اینجا دریافت کنید: state-management-with-mobx-part3.zip