اشتراک‌ها
ویندوز 8 و فروش صد میلیون نسخه تاکنون!
علی‌رغم پیش‌بینی‌ها و تحلیل‌های ناگوار برای ویندوز 8، طبق آخرین آمار اعلام شده توسط مایکروسافت، تاکنون 100 میلیون نسخه از این ویندوز به فروش رسیده است. باید دید بازار داغ تبلت‌ها و افزایش تمایل مصرف‌کنندگان به این نوع سخت‌افزار، ویندوز 8 و دیگر رقبا را به کجا خواهد رساند.
ویندوز 8 و فروش صد میلیون نسخه تاکنون!
نظرات مطالب
ارسال پیام های تبلیغاتی به Telegram با استفاده از #C
تا 100 تای اول Free هستش ، شاید به این دلیل هست که کمی با وقفه پیام هارو ارسال میکنه.
در ضمن این نکته رو فراموش نکنید که طول رشته‌ی ارسالی نباید بیشتر از 1000 کاراکتر باشه.
من در این مورد به پشتیبانی سایت تیکت فرستادم اگر جواب دادند،به اشتراک میزارم.
نظرات مطالب
سرعت واکشی اطلاعات در List و Dictionary
نه. ToList یا ToDictionary اصطلاحا یک نوع Projection هستند و پس از دریافت اطلاعات مطابق کوئری لینک شما اعمال خواهند شد (شکل دادن به اطلاعات دریافت شده از بانک اطلاعاتی؛ فرضا 100 رکورد دریافت شده، حالا شما خواستید از این رکوردها برای استفاده، List درست کنید یا دیکشنری یا حالت‌های دیگر).
نظرات مطالب
تعیین شماره نگارش IE مورد استفاده در Web Browser Control
ممنون. حداقل دو گزینه سورس باز 100 درصد تهیه شده با کدهای دات نت نیز برای رندر HTML وجود دارند:
الف) تبدیلگر HTML به XAML نوشته شده توسط خود مایکروسافت
ب) پروژه فوق العاده جالب HTML Renderer 
نظرات مطالب
سایت‌های مهمی که از ASP.NET MVC استفاده می‌کنند
الگو که همان view state خودش یک الگو است. از این افزونه استفاده کنید تا حجم view state را هم مشاهده کنید: (^).
به عبارتی اگر وب سرور IIS بود و View State هم حجمی را نمایش داد، سایت 100 درصد بر مبنای ASP.NET Web forms است.
نظرات مطالب
بررسی علت CPU Usage بالای برنامه در حال اجرا
ممنون آقای نصیری
از کجا میشه درصد استفاده از CPU رو متوجه شد؟ وقتی CPU Usage ما به 100 درصد میرسه نه میشه به سرور کانکت شد و نه اینکه وقتی کانکتیم میذاره برنامه ای رو اجرا کنیم. بخاطر همین مجبوریم وقنی درصد از حدی بالاتر رفت بلافاصله یه سری اطلاعات را ثبت کنیم.
مطالب
آموزش TypeScript #5
در ادامه مباحث شی گرایی در TypeScript قصد داریم به مباحث مربوط به interface و طریقه استفاده از آن بپردازیم. همانند زبان‌های دات نتی در TypeScript نیز به راحتی می‌توانید interface تعریف کنید. در یک اینترفیس اجازه پیاده سازی هیچ تابعی را ندارید و فقط باید عنوان و پارامتر‌های ورودی و نوع خروجی آن را تعیین کنید. برای تعریف اینترفیس از کلمه کلیدی interface به صورت زیر استفاده خواهیم کرد.
export interface ILogger {
  log(message: string): void;
}
همان طور در پست‌های قبلی مشاهده شد از کلمه کلیدی export برای عمومی کردن اینترفیس استفاده می‌کنیم. یعنی این اینترفیس از بیرون ماژول خود نیز قابل دسترسی است.
حال نیاز به کلاسی داریم که این اینترفیس را پیاده سازی کند. این پیاده سازی به صورت زیر انجام می‌گیرد:
export class Logger implements ILogger
{
}
یا:
export class AnnoyingLogger implements ILogger {
   log(message: string): void{
         alert(message);
     } 
}
همانند دات نت یک کلاس می‌تواند چندین اینترفیس را پیاده سازی کند.(اصطلاحا به این روش explicit implementation یا پیاده سازی صریح می‌گویند)
export class MyClass implements IFirstInterface, ISecondInterface
{

}
*یکی از قابلیت جالب و کارآمد زبان TypeScript این است که در هنگام کار با اینترفیس‌ها حتما نیازی به پیاده سازی صریح نیست. اگر یک object تمام متغیر‌ها و توابع مورد نیاز یک اینترفیس را پیاده سازی کند به راحتی همانند روش explicit emplementation می‌توان از آن object استفاده کرد.  به این قابلیت Duck Typing  می‌گویند. مثال:
IPerson {
   firstName: string;
   lastName: string;
} 
class Person implements IPerson {
  constructor(public firstName: string, public lastName: string) {
  }
}

varpersonA: IPerson = newPerson('Masoud', 'Pakdel'); //expilict
varpersonB: IPerson = { firstName: 'Ashkan', lastName: 'Shahram'}; // duck typing
همان طور که می‌بینید object  دوم به نام personB تمام متغیر‌ها ی مورد نیاز اینترفیس IPerson را پیاده سازی کرده است در نتییجه کامپایلر همان رفتاری را که با object اول به نام personA دارد را با آن نیز خواهد داشت.

پیاده سازی چند اینترفیس به صورت همزمان
همانند دات نت که یک کلاس فقط می‌تواند از یک کلاس ارث ببرد ولی می‌تواند n  تا اینترفیس را پیاده سازی کند در TypeScript نیز چنین قوانینی وجود دارد. یعنی یک اینترفیس می‌تواند چندین اینترفیس دیگر را توسعه دهد(extend) و کلاسی که این اینترفیس را پیاده سازی می‌کند باید تمام توابع اینترفیس‌ها را پیاده سازی کند. مثال:
interface IMover {
 move() : void;
}

interface IShaker {
 shake() : void;
} 

interface IMoverShaker extends IMover, IShaker {
}
class MoverShaker implements IMoverShaker {
 move() {
 }
 shake() {
 }
}
*به کلمات کلیدی extends و implements و طریقه به کار گیری آن‌ها دقت کنید.

 instanceof

از instanceof زمانی استفاده می‌کنیم که قصد داشته باشیم که یک instance را با یک نوع مشخص مقایسه کنیم. اگر instance مربوطه از نوع مشخص باشد یا از این نوع ارث برده باشد مقدار true برگشت داده می‌شود در غیر این صورت مقدار false خواهد بود.
یک مثال:
var isLogger = logger instanceof Utilities.Logger; 
var isLogger = logger instanceof Utilities.AnnoyingLogger; 
var isLogger = logger instanceof Utilities.Formatter;
Method overriding
در TypeScript می‌توان مانند زبان‌های شی گرای دیگر Method overriding را پیاده سازی کرد. یعنی می‌توان متد‌های کلاس پایه را در کلاس مشتق شده تحریف کرد. با یک مثال به شرح این مورد خواهم پرداخت.
فرض کنید یک کلاس پایه به صورت زیر داریم:
class BaseEmployee
{   
    constructor (public fname: string,public lname: string) 
    {  
    }  
    sayInfo() 
    {  
       alert('this is base class method');
    }  
}
کلاس دیگری به نام Employee می‌سازیم که کلاس بالا را توسعه می‌دهد(یا به اصطلاح از کلاس بالا ارث می‌برد).
class Employee extends BaseEmployee
{  
   sayInfo() 
     {  
        alert('this is derived class method');
     }  
}  

window.onload = () =>  
{   
    var first: BaseEmployee= new Employee();     
    first.sayInfo();  
    var second: BaseEmployee = new BaseEmployee(); 
    second.sayInfo(); 
}
نکته مهم این است که دیگر خبری از کلمه کلیدی virtual برای مشخص کردن توابعی که قصد overriding آن‌ها را داریم نیست. تمام توابع که عمومی هستند را می‌توان override کرد.
*اگر در کلاس مشتق شده قصد داشته باشیم که به توابع و فیلد‌های کلاس پایه اشاره کنیم باید از کلمه کلیدی super استفاده کنیم.(معادل base در #C).
مثال:
class Animal {
    constructor (public name: string) {
    }
}

class Dog extends Animal {    
      constructor (public name: string, public age:number)
      {
        super(name);
      }

    sayHello() {
alert(super.name);
 } }
اگر به سازنده کلاس مشتق شده دقت کنید خواهید دید که پارامتر name را به سازنده کلاس پایه پاس دادیم: کد معادل در #C به صورت زیر است:
public class Dog : Animal 
{    
      public Dog (string name, int age):base(name)
      {
      }
}
در تابع sayHello نیز با استفاده از کلمه کلیدی super به فیلد name در کلاس پایه دسترسی خواهیم داشت.

*دقت کنید که مباحث مربوط به interface و private modifier و Type safety که پیش‌تر در مورد آن‌ها بحث شد، فقط در فایل‌های TypeScript و در هنگام کد نویسی و طراحی معنی دار هستند، زیرا بعد از کامپایل فایل‌های ts این مفاهیم در Javascript پشتیبانی نمی‌شوند در نتیجه هیچ مورد استفاده هم نخواهد داشت.

ادامه دارد...
مطالب
چند ستونه کردن در iTextSharp

فرض کنید جدولی دارید با چند ستون محدود که نتیجه‌ی نهایی گزارش آن مثلا 100 صفحه است. جهت صرفه جویی در کاغذ مصرفی شاید بهتر باشد که این جدول را به صورت چند ستونی مثلا 5 ستون در یک صفحه نمایش داد؛ چیزی شبیه به شکل زیر:


روش انجام اینکار به کمک iTextSharp به صورت زیر است:


using System;

using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;
using System.Diagnostics;

class Program
{
static void Main(string[] args)
{
using (var pdfDoc = new Document(PageSize.A4))
{
var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));
pdfDoc.Open();

var table1 = new PdfPTable(1);
table1.WidthPercentage = 100f;
table1.HeaderRows = 2;
table1.FooterRows = 1;

//header row
var headerCell = new PdfPCell(new Phrase("header"));
table1.AddCell(headerCell);

//footer row
var footerCell = new PdfPCell(new Phrase(" "));
table1.AddCell(footerCell);

//adding some rows
for (int i = 0; i < 400; i++)
{
var rowCell = new PdfPCell(new Phrase(i.ToString()));
table1.AddCell(rowCell);
}

// wrapping table1 in multiple columns
ColumnText ct = new ColumnText(pdfWriter.DirectContent);
ct.RunDirection = PdfWriter.RUN_DIRECTION_RTL;
ct.AddElement(table1);

int status = 0;
int count = 0;
int l = 0;
int columnsWidth = 100;
int columnsMargin = 7;
int columnsPerPage = 4;
int r = columnsWidth;
bool isRtl = true;

// render the column as long as it has content
while (ColumnText.HasMoreText(status))
{
if (isRtl)
{
ct.SetSimpleColumn(
pdfDoc.Right - l, pdfDoc.Bottom,
pdfDoc.Right - r, pdfDoc.Top
);
}
else
{
ct.SetSimpleColumn(
pdfDoc.Left + l, pdfDoc.Bottom,
pdfDoc.Left + r, pdfDoc.Top
);
}

var delta = columnsWidth + columnsMargin;
l += delta;
r += delta;

// render as much content as possible
status = ct.Go();

// go to a new page if you've reached the last column
if (++count > columnsPerPage)
{
count = 0;
l = 0;
r = columnsWidth;
pdfDoc.NewPage();
}
}
}

//open the final file with adobe reader for instance.
Process.Start("Test.pdf");
}
}


توضیحات:
تا قسمت تعریف جدول و اضافه کردن سطرها و ستون‌های مورد نظر، همان بحث «تکرار خودکار سرستون‌های یک جدول در صفحات مختلف، توسط iTextSharp» می‌باشد.
اصل مطلب از قسمت ColumnText شروع می‌شود. با استفاده از شیء ColumnText می‌توان محتوای خاصی را در طی چند ستون در صفحه نمایش داد. عرض این ستون‌ها هم توسط متد SetSimpleColumn مشخص می‌شود و همچنین محل دقیق قرارگیری آن‌ها در صفحه. در اینجا دو حالت راست به چپ و چپ به راست در نظر گرفته شده است.
اگر حالت راست به چپ را در نظر بگیریم، محل قرارگیری اولین ستون از سمت راست صفحه (pdfDoc.Right) باید تعیین شود. سپس هربار به اندازه‌ی عرضی که مد نظر است باید محل شروع ستون را مشخص کرد (pdfDoc.Right - l). هر زمانیکه ct.Go فراخوانی می‌شود، تاجایی که میسر باشد، اطلاعات جدول 1 در یک ستون درج می‌شود. سپس بررسی می‌شود که تا این لحظه چند ستون در صفحه نمایش داده شده است. اگر تعداد مورد نظر ما (columnsPerPage) تامین شده باشد، کار را در صفحه‌ی بعد ادامه خواهیم داد (pdfDoc.NewPage)، در غیراینصورت مجددا مکان یک ستون دیگر در همان صفحه تعیین شده و کار افزودن اطلاعات به آن آغاز خواهد شد و این حلقه تا جایی که تمام محتوای جدول 1 را درج کند، ادامه خواهد یافت.


مطالب
کارهایی جهت بالابردن کارآیی Entity Framework #2

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

عدم استفاده از کوئری‌های کلی

فرض کنید در یک فرم جستجو، 4 تکست باکس FirstName, LastName, City و PostalZipCode برای عملیات جستجو در نظر گرفته شده است و کاربر می‌تواند بر اساس آنها جستجو را انجام دهد.

var searchModel = new Pupil
{
    FirstName = "Ben",
    LastName = null,
    City = null,
    PostalZipCode = null
};

List<Pupil> pupils = db.Pupils.Where(p =>
        (searchModel.FirstName == null || p.FirstName == searchModel.FirstName)
        && (searchModel.LastName == null || p.LastName == searchModel.LastName)
        && (searchModel.City == null || p.LastName == searchModel.City)
        && (searchModel.PostalZipCode == null || p.PostalZipCode == searchModel.PostalZipCode))
    .Take(100)
    .ToList()
در کد بالا کاربر فقط فیلد نام را برای جستجو انتخاب کرده است و سایر فیلدها را خالی در نظر گرفته است و کوئری بالا صادر شده است و دستورات زیر به اس کیو ال ارسال شده‌اند:
 USE [EFSchoolSystem]
DECLARE @p__linq__0 NVarChar(4000) SET @p__linq__0 = 'Ben'
DECLARE @p__linq__1 NVarChar(4000) SET @p__linq__1 = 'Ben'
DECLARE @p__linq__2 NVarChar(4000) SET @p__linq__2 = ''
DECLARE @p__linq__3 NVarChar(4000) SET @p__linq__3 = ''
DECLARE @p__linq__4 NVarChar(4000) SET @p__linq__4 = ''
DECLARE @p__linq__5 NVarChar(4000) SET @p__linq__5 = ''
DECLARE @p__linq__6 NVarChar(4000) SET @p__linq__6 = ''
DECLARE @p__linq__7 NVarChar(4000) SET @p__linq__7 = ''

-- Executed query
SELECT TOP (100)
        [Extent1].[PupilId] AS [PupilId] ,
        [Extent1].[FirstName] AS [FirstName] ,
        [Extent1].[LastName] AS [LastName] ,
        [Extent1].[Address1] AS [Address1] ,
        [Extent1].[Adderss2] AS [Adderss2] ,
        [Extent1].[PostalZipCode] AS [PostalZipCode] ,
        [Extent1].[City] AS [City] ,
        [Extent1].[PhoneNumber] AS [PhoneNumber] ,
        [Extent1].[SchoolId] AS [SchoolId] ,
        [Extent1].[Picture] AS [Picture]
FROM    [dbo].[Pupils] AS [Extent1]
WHERE   (@p__linq__0 IS NULL OR [Extent1].[FirstName] = @p__linq__1)
        AND (@p__linq__2 IS NULL OR [Extent1].[LastName] = @p__linq__3)
        AND (@p__linq__4 IS NULL OR [Extent1].[LastName] = @p__linq__5)
        AND (@p__linq__6 IS NULL OR [Extent1].[PostalZipCode] = @p__linq__7)
هنگامیکه کوئری، سمت اس کیو ال سرور اجرا می‌شود، در صورتیکه پلن مربوط به آن وجود نداشته باشد، اس کیو ال سرور اقدام به ایجاد پلن، براساس پارامترهای ورودی می‌کند و از این به بعد، کوئری‌هایی یکسان، با پارامترهای متفاوت، از پلن کش شده استفاده خواهند کرد و از ایجاد پلن دیگری بدلیل هزینه بر بودن آن جلوگیری می‌شود.
مشکلی که در این دسته از کوئری‌های عمومی ایجاد می‌گردد آن است که ممکن است پلنی که برای یک گروه از پارامترهای ورودی مناسب باشد (جستجو بر اساس نام) برای سایر پارامترهای ورودی نامناسب باشد. تصور کنید تمام دانش آموزان، در شهر نیویورک یا بوستون زندگی می‌کنند. بنابراین این ستون از تنوع انتخاب کمتری برخوردار است در مقایسه با ستون نام خانوادگی و فرض کنید پلن، براساس پارامتر شهر ایجاد شده است. بنابراین ایجاد این پلن برای سایر پارامترها از کارآیی کافی برخوردار نیست. این مشکل با نام Bad Parameter Sniffing شناخته می‌شود و درباره‌ی Parameter Sniffing در اینجا به تفصیل اشاره شده است.
این مشکل زمانی بیشتر مشکل ساز خواهد شد که 99 درصد دانش آموزان در شهر نیویورک و فقط 1 درصد آن‌ها در شهر بوستون زندگی می‌کنند و پلن ایجاد شده بر اساس پارامتر شهر بوستون باشد.

راه حل اول:
برای حل این مشکل تنها یک راه حل خاص وجود ندارد و باید براساس شرایط برنامه، کوئری از حالت عمومی خارج گردد؛ مانند زیر:
 if (searchModel.City == null)
{
    pupils = db.Pupils.Where(p =>
        (searchModel.FirstName == null || p.FirstName == searchModel.FirstName)
        && (searchModel.LastName == null || p.LastName == searchModel.LastName)
        && (searchModel.PostalZipCode == null || p.PostalZipCode == searchModel.PostalZipCode))
        .Take(100)
        .ToList();
}
else
{
    pupils = db.Pupils.Where(p =>
        (searchModel.FirstName == null || p.FirstName == searchModel.FirstName)
        && (searchModel.LastName == null || p.LastName == searchModel.LastName)
        && (searchModel.City == null || p.LastName == searchModel.City)
        && (searchModel.PostalZipCode == null || p.PostalZipCode == searchModel.PostalZipCode))
        .Take(100)
        .ToList();
}

راه حل دوم:

کامپایل مجدد پلن در اجرای هر کوئری، اما این راه حل سرباری را تحمیل می‌کند. بدین منظور مترجم زیر را ایجاد کنید:
public class RecompileDbCommandInterceptor : IDbCommandInterceptor
{
    public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
    {
        if(!command.CommandText.EndsWith(" option(recompile)"))
        {
            command.CommandText += " option(recompile)";
        }
    }
    //and implement other interface members
}
و مانند زیر استفاده کنید:
 var interceptor = new RecompileDbCommandInterceptor();
DbInterception.Add(interceptor);
var pupils = db.Pupils.Where(p => p.City = city).ToList();
DbInterception.Remove(interceptor);

راه حل سوم:

استفاده از اجرای به تعویق افتاده به شکل زیر است:

var result = db.Pupils.AsQueryable();

if(searchModel.FirstName != null )
     result = result.Where(p => p.FirstName == searchModel.FirstName);

if(searchModel.LastName != null )
     result = result.Where(p => p.LastName == searchModel.LastName);

if(searchModel.PostalZipCode != null )
     result = result.Where(p => p.PostalZipCode == searchModel.PostalZipCode);

if(searchModel.City != null )
     result = result.Where(p => p.City == searchModel.City);

افزونگی کشِ پلن

استفاده‌ی مجدد از پلن بدلیل عدم ایجاد مجدد آن در زمان اجرای هر کوئری، بسیار خوب است. برای استفاده‌ی مجدد از پلن، باید دستورات ارسالی یکسان باشند؛ مانند کوئری‌های پارامتریک. در EF هنگامیکه از متغیرها استفاده کنید، کوئری‌ها پارامتریک تولید می‌کند؛ اما یک استثناء وجود دارد: ()Skip و ()Take

2 متد فوق بیشتر جهت صفحه بندی استفاده می‌شوند:

var schools = db.Schools
    .OrderBy(s => s.PostalZipCode)
    .Skip(model.Page * model.ResultsPerPage)
    .Take(model.ResultsPerPage)
    .ToList();
فرض کنید مقدار ResultsPerPage برابر 100 باشد و مقدار Page برابر 417. کوئری ارسالی به شکل زیر خواهد بود:

حال اگر قصد دریافت اطلاعات صفحه‌ی 500 را داشته باشید، مقادیر کوئری بعدی بترتیب 100 و 50000 خواهد بود و بجای مقادیر تصویر بالا 100 و 50000 قرار داده می‌شوند و کوئری متفاوتی با پلن متفاوتی ایجاد خواهد شد و اس کیو ال پلن کوئری قبلی را مورد استفاده قرار نخواهد داد و با اجرای کوئری دوم، پلن متفاوتی ایجاد خواهد کرد که این باعث ایجاد افزونگی پلن‌ها خواهد شد و همانگونه که قبلا اشاره شد ایجاد پلن جدید هزینه بر است.

نکته: جهت مشاهده پلن‌های کش شده در اس کیو ال، دستور زیر اجرا کنید:
SELECT  text, query_plan
FROM    sys.dm_exec_cached_plans
        CROSS APPLY sys.dm_exec_query_plan(plan_handle)
        CROSS APPLY sys.dm_exec_sql_text(plan_handle)
درصورتیکه کوئری دوم را برای صفحه 500 اجرا کنید، مشاهده خواهید کرد پلن کش شده‌ی دیگری ایجاد شده است. این افزونگی، نامناسب است به دو دلیل:
  1. صدمه به کارآیی؛ هربار EF یک کوئری و اس کیو ال یک پلن جدید را ایجاد می‌کنند.
  2. افزایش اشغال حافظه؛ کش شدن کوئری‌ها توسط EF سمت کلاینت و کش شدن پلن‌ها در اس کیو ال سرور (کش بی رویه‌ی پلن‌ها باعث حذف سایر پلن‌های مورد استفاده بدلیل محدودیت حافظه می‌شود که امکان بروز اختلال در کارآیی را به‌همراه خواهد داشت.)


علت بروز مشکل:

هنگامیکه یک مقدار int، به متدهای ()Skip و ()Take ارسال می‌شود، EF نمی‌تواند تشخیص دهد این مقدار ارسالی ثابت (absolute) مانند (100)Take است یا توسط یک متغیر مانند (متغیر)Take تولید شده است. به همین خاطر EF مقدار ارسال شده را پارامتریک در نظر نمی‌گیرد.


راه حل:

در EF6 پیاده سازی دیگری برای متدهای ()Skip و ()Take ارائه شده است که برای حل مشکل فوق می‌توان به کار گرفت، این پیاده سازی امکان دریافت lambada بجای int را دارد که باعث ایجاد کوئری‌های پارامتریک خواهد شد.

int resultsToSkip = model.Page * model.ResultsPerPage;
var schools = db.Schools
    .OrderBy(s => s.PostalZipCode)
    .Skip(() => resultsToSkip) //must pre-calculate this value
    .Take(() => model.ResultsPerPage)
    .ToList();
اکنون کوئری ارسال شده به اس کیو ال به صورت زیر خواهد بود:

همانطور که مشاهده می‌کنید این بار  EF کوئری پارامتریک ایجاد و ارسال کرده است.
مطالب
ساخت یک مثال Todo با MobX و React

پیشنیاز این مطلب مطالعه قسمت MobX می‌باشد. در این مثال قصد داریم  یک برنامه‌ی Todo را با استفاده از MobX و React ایجاد کنیم.


ایجاد ساختار ابتدایی پروژه

برای ساخت پروژه، به خط فرمان مراجعه کرده و با دستور زیر، یک پروژه‌ی react از نوع typescript را ایجاد می‌کنیم. 

npx create-react-app todo-mobx --template typescript 
cd todo-mobx


برای توسعه‌ی این مثال، از محیط توسعه‌ی VSCode استفاده می‌کنیم. اگر VSCode بر روی سیستم شما نصب باشد، در همان مسیری که خط فرمان باز است، دستور زیر را اجرا کنید؛ پروژه‌ی شما در VSCode باز می‌شود:

code


سپس در محیط VSCode، دکمه‌های ctrl+` را فشرده (ctrl+back-tick) و دستورات زیر را در ترمینال ظاهر شده وارد کنید:

npm install --save-dev typescript @types/node @types/react @types/react-dom @types/jest
npm install  mobx mobx-react-lite --save


در ادامه برای استایل بندی بهتر برنامه از کتابخانه‌های bootstrap و  font-awesome استفاده می‌کنیم: 

npm install bootstrap --save
npm install font-awesome --save

سپس فایل index.tsx را باز کرده و دو خط زیر را به آن اضافه می‌کنیم: 

import "bootstrap/dist/css/bootstrap.css";
import "font-awesome/css/font-awesome.css";


کتابخانه‌ی MobX، از تزئین کننده‌ها یا decorators استفاده می‌کند. بنابراین نیاز است به tsconfig پروژه مراجعه کرده و خط زیر را به آن اضافه کنیم:  

"compilerOptions": {
   .... ,
   "experimentalDecorators": true
}


ایجاد مخازن حالت MobX

در ادامه نیاز است store‌های MobX را ایجاد کنیم و بعد آن‌ها را به react اتصال دهیم. بدین منظور یک پوشه‌ی جدید را در مسیر src، به نام stores ایجاد می‌کنیم و سپس فایل جدیدی را به نام todo-item.ts در آن با محتوای زیر ایجاد می‌کنیم: 

import { observable, action } from "mobx";

export default class TodoItem {
    id = Date.now();

    @observable text: string = '';
    @observable isDone: boolean = false;

    constructor(text: string) {
        this.text = text;
    }

    @action
    toggleIsDone = () => {
        this.isDone = !this.isDone
    }

    @action
    updateText = (text: string) => {
        this.text = text;
    }
}


در همان مسیر stores، فایل دیگری را نیز به نام todo-list.ts، با محتوای زیر ایجاد می‌کنیم:

import { observable, computed, action } from "mobx";

import TodoItem from "./todo-item";

export class TodoList {
    @observable.shallow list: TodoItem[] = [];

    constructor(todos: string[]) {
        todos.forEach(this.addTodo);
    }

    @action
    addTodo = (text: string) => {
        this.list.push(new TodoItem(text));
    }

    @action
    removeTodo = (todo: TodoItem) => {
        this.list.splice(this.list.indexOf(todo), 1);
    };

    @computed
    get finishedTodos(): TodoItem[] {
        return this.list.filter(todo => todo.isDone);
    }

    @computed
    get openTodos(): TodoItem[] {
        return this.list.filter(todo => !todo.isDone);
    }
}

توضیحات:

مفهوم observable@: کل شیء state را به صورت یک شیء قابل ردیابی JavaScript ای ارائه می‌کند.

مفهوم computed@: این نوع خواص، مقدار خود را زمانیکه observable‌های وابسته‌ی به آن‌ها تغییر کنند، به روز رسانی می‌کنند.

مفهوم action@: جهت به روز رسانی state و سپس نمایش تغییرات یا نمایش نمونه‌ی دیگری در DOM می‌باشند.



برپایی Context
در این مثال از شیء Provider خود MobX استفاده نمی‌کنیم؛ بلکه از React Context استفاده می‌کنیم. به همین جهت در مسیر src، یک پوشه‌ی جدید دیگر را به نام Providers ایجاد می‌کنیم. سپس فایلی را به نام store-provider.ts ایجاد کرده و کدهای زیر را به آن اضافه می‌کنیم: 
import { createContext, useContext } from "react";
import { TodoList } from "../stores/todo-list";

export const StoreContext = createContext<TodoList>({} as TodoList);
export const StoreProvider = StoreContext.Provider;

export const useStore = (): TodoList => useContext(StoreContext);
توضیحات:
- در اینجا StoreContext را ایجاد کرده و سپس به آن یک مقدار پیش فرض از نوع یک object خالی را ارسال کرده‌ایم.
- سپس بر اساس آن، شیء StoreProvider را که از نوع ReactConxtext می‌باشد، ایجاد کردیم. 
- متد useStore که به صورت export و نوعی از useContext می‌باشد، برای دسترسی ساده‌تر به Context معرفی شده‌است که در ادامه کاربرد آن‌را خواهید دید.
- برای اعمال StoreProvider در شروع کننده‌ی برنامه React، به فایل index.tsx مراجعه کرده و آن‌را به صورت زیر ویرایش می‌کنیم: 
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import "bootstrap/dist/css/bootstrap.css";
import "font-awesome/css/font-awesome.css";
import { TodoList } from './stores/todo-list';
import { StoreProvider } from './providers/store-provider';


const todoList = new TodoList([
    'Read Book',
    'Do exercise',
    'Watch Walking dead series'
]);

ReactDOM.render(
    <StoreProvider value={todoList}>
        <App />
    </StoreProvider>
    , document.getElementById('root'));

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
توضیحات:  StoreProvider ای را که در فایل store-provider.ts ایجاد کردیم، در اینجا به شروع کننده‌ی React معرفی می‌کنیم و سه مقدار پیش فرض را نیز به آن اعمال می‌کنیم.

افزودن کامپوننت‌های برنامه
برای نمایش لیست Todo‌ها و عملیات حذف، اضافه و ویرایش، نیاز به سه کامپوننت تابعی را داریم: 

اضافه کردن کامپوننت TodoNew
در مسیر src، یک پوشه‌ی جدید را به نام components ایجاد می‌کنیم. سپس فایلی را در آن به نام TodoNew.tsx ایجاد کرده و کدهای زیر را به آن اضافه می‌کنیم: 
import React, { useState } from 'react';
import { useStore } from '../providers/store-provider';

export const TodoNew = () => {
    const [newTodo, setTodo] = useState('');
    const todoList = useStore();

    const addTodo = () => {
        todoList.addTodo(newTodo);
        setTodo('');
    };

    return (

        <div className="input-group mb-3">
            <input type="text" className="form-control" placeholder="Add To do" value={newTodo} onChange={(e) => setTodo(e.target.value)} />
            <div className="input-group-append">
                <button className="btn btn-success" type="submit" onClick={addTodo}>Add Todo</button>
            </div>
        </div>
    )
};
توضیحات: 
- useStore ای را که در مرحله‌ی قبل ایجاد کردیم، در اینجا برای دسترسی به state‌های MobX استفاده می‌کنیم.
- در input و رویداد onChange آن، مقدار ورودی کاربر را به متد newTodo اعمال می‌کنیم و بعد از اینکه کاربر دکمه‌ی Add Todo را زد، مقدار newTodo را به تابع addTodo که در useStore می‌باشد، اعمال می‌کنیم.

افزودن کامپوننت نمایش لیست کارها: TodoList
در مسیر src و پوشه‌ی components آن، فایل جدیدی را به نام TodoList.tsx ایجاد کرده و کدهای زیر را به آن اضافه می‌کنیم: 
import React from 'react';

import { TodoItem } from "./TodoItem";
import { useObserver } from "mobx-react-lite";
import { useStore } from '../providers/store-provider';

export const TodoList = () => {
    const todoList = useStore();

    return useObserver(() => (
        <div>
            <h1>Open Todos</h1>
            <table className="table">
                <thead className="thead-dark">
                    <tr>
                        <th>Name</th>
                        <th className="text-left">Do It?</th>
                        <th>Actions</th>
                    </tr>
                </thead>
                <tbody>
                    {
                        todoList.openTodos.map(todo =>
                            <tr key={`${todo.id}-${todo.text}`}>
                                <TodoItem todo={todo} />
                            </tr>)
                    }

                </tbody>
            </table>

            <h1>Finished Todos</h1>
            <table className="table">
                <thead className="thead-light">
                    <tr>
                        <th>Name</th>
                        <th className="text-left">Do It?</th>
                        <th>Actions</th>
                    </tr>
                </thead>
                <tbody>
                    {
                        todoList.finishedTodos.map(todo =>
                            <tr key={`${todo.id}-${todo.text}`}>
                                <TodoItem todo={todo} />
                            </tr>)
                    }

                </tbody>
            </table>
        </div>
    ));
};
توضیحات:
- useStore را به ثابت todoList انتساب می‌دهیم.
- سپس برای نمایش Todo ‌ها، یک جدول را طراحی می‌کنیم و همچنین برای نمایش کارهای تکمیل شده (Finish Todo) جدول دیگری را ایجاد می‌کنیم.
- در کلاس TodoList، که پیشتر آن‌را ایجاد کردیم، از دو خاصیت openTodos و finishedTodos از نوع get که با Decorator از نوع computed@ هستند، برای نمایش Open Todos و Finished Todos استفاده می‌کنیم. خروجی این خواص، لیستی از نوع TodoItem می‌باشند که با کمک متد map، به فیلد‌های TodoItem آن‌ها دسترسی پیدا می‌کنیم.

برای منظم کردن کدها، کامپوننت دیگری را در مسیر src/components به نام TodoItem.tsx ایجاد کرده و کدهای زیر را به آن اضافه می‌کنیم: 
import React, { useState } from 'react';
import TodoItemClass from "../stores/todo-item";


import { useStore } from '../providers/store-provider';

interface Props {
    todo: TodoItemClass;
}

export const TodoItem = ({ todo }: Props) => {
    const todoList = useStore();
    const [newText, setText] = useState('');
    const [isEditing, setEdit] = useState(false);

    const saveText = () => {
        todo.updateText(newText);
        setEdit(false);
        setText('');
    };

    return (
        <React.Fragment>
            {
                isEditing ?
                    <React.Fragment>
                        <td>
                            <input className="form-control" placeholder={todo.text} type="text" onChange={(e) => setText(e.target.value)} />
                        </td>
                        <td></td>
                        <td>
                            <button className="btn btn-xs btn-success " onClick={saveText}>Save</button>
                        </td>
                    </React.Fragment>
                    :
                    <React.Fragment>
                        <td>
                            {todo.text}
                        </td>

                        <td className="text-left">
                            <input className="form-check-input" type="checkbox" onChange={todo.toggleIsDone} defaultChecked={todo.isDone}></input>
                        </td>
                        <td>
                            <button className="btn btn-xs btn-warning " onClick={() => setEdit(true)}>
                                <i className="fa fa-edit"></i>
                            </button>
                            <button className="btn btn-xs btn-danger ml-2" onClick={() => todoList.removeTodo(todo)}>
                                <i className="fa fa-remove"></i>
                            </button>
                        </td>
                    </React.Fragment>
            }

        </React.Fragment>

    )
};
توضیحات:
- در کامپوننت قبلی TodoList.tsx، متدهای TodoItem را به کامپوننت TodoItem.tsx پاس داده و آن را در دو حالت ویرایش و نمایش، نشان می‌دهیم.
- در جدول، امکان ویرایش، حذف و ثبت رکوردها را قرار داده‌ایم. برای ویرایش، مقدار input وارد شده را به متد (todo.updateText(newText پاس می‌دهیم و برای حذف، (todoList.removeTodo(todo را فراخوانی می‌کنیم.
 
کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید  Github