مطالب
آشنایی با NuGet - قسمت اول

NuGet چیست؟

روش متداول استفاده از کتابخانه‌های موجود دات نتی در Visual studio‌ عموما به این صورت است: مراجعه به سایت مربوطه، دریافت بسته مورد نظر، باز کردن آن و سپس افزودن ارجاعی به اسمبلی‌های آن کتابخانه. در این حالت زمانیکه نسخه‌ی جدیدی از کتابخانه‌ی مورد استفاده ارائه ‌شود (و عموما تا مدت‌ها شاید از آن بی‌اطلاع باشیم) تمام این مراحل باید از ابتدا تکرار شوند و همینطور الی آخر.
برای رفع این نقیصه، تیم ASP.NET، افزونه‌ای سورس باز و رایگان را به نام NuGet جهت VS.Net 2010 طراحی کرده‌اند که کار مدیریت بسته‌های کتابخانه‌های مورد استفاده را بسیار ساده کرده است. امکانات این افزونه پس از نصب، در دو حالت استفاده از رابط گرافیکی کاربری آن و یا با استفاده از خط فرمان PowerShell ویندوز در دسترس خواهد بود. این افزونه در زمان بارگذاری، با مراجعه به فید سایت مرکزی خود، لیست بسته‌های مهیا را در اختیار علاقمندان قرار می‌دهد. درب این سایت مرکزی به روی تمام توسعه‌ دهنده‌ها جهت افزودن بسته‌های خود باز است.
و ... فراگیری کار با NuGet برای تمام برنامه نویسان دات نت لازم و ضروری است! از این جهت که پیغام "این بسته تنها برای NuGet عرضه شده است" کم کم در حال متداول شدن می‌باشد و دیگر سایت‌های مرتبط، لینک مستقیمی را جهت دریافت کتابخانه‌های خود ارائه نمی‌دهند. حتی خبر به روز شدن محصولات خود را هم شاید دیگر به صورت منظم ارائه ندهند؛ زیرا NuGet کار مدیریت آن‌ها را به عهده خواهد داشت.


دریافت و نصب NuGet

NuGet را حداقل به سه طریق می‌توان دریافت و نصب کرد:
الف) با مراجعه به سایت CodePlex : (+)
ب) دریافت آن از سایت گالری‌های آن : (+)


ج) با استفاده از امکانات VS.NET

هر سه روش فوق به دریافت و نصب فایل NuGet.Tools.vsix منتهی می‌شوند. برای مثال در روش (ج) باید به منوی Tools و گزینه‌ی Extension Manager مراجعه کنید. سپس برگه‌ی Online Gallery را گشوده و اندکی صبر کنید تا اطلاعات آن دریافت و نمایش داده شود. سپس NuGet را در Search box بالای صفحه نوشته و NuGet Package manager ظاهر شده را انتخاب و نصب کنید.



نحوه استفاده از NuGet

فرض کنید یک پروژه جدید ASP.NET را ایجاد کرده‌اید و نیاز است تا کتابخانه‌ی ELMAH به آن اضافه شود. روش انجام اینکار را به کمک NuGet در ادامه بررسی خواهیم کرد (کمتر از یک دقیقه زمان خواهد برد):

الف) با کمک امکانات رابط گرافیکی کاربر آن
ساده‌ترین روش استفاده از NuGet ، کلیک راست بر روی پوشه References در Solution explorer و سپس انتخاب گزینه‌ی Add Library Package Reference می‌باشد:



در صفحه‌ی باز شده، برگه‌ی Online را باز کنید و مدتی صبر نمائید تا اطلاعات لازم دریافت گردد (در زمان نگارش این مطلب، 1135 بسته در این مخزن موجود است):



سپس در جعبه‌ی جستجوی سمت راست بالای صفحه، نام کتابخانه‌ی مورد نظر را نوشته و اندکی صبر کنید تا اطلاعات آن نمایش داده شود:



اکنون با کلیک بر روی دکمه Install ، بسته مرتبط با این کتابخانه دریافت شده و سپس به صورت خودکار ارجاعی به آن نیز افزوده خواهد شد. همچنین تنظیمات مرتبط با فایل Config برنامه هم اضافه می‌شوند.

روش دیگر ظاهر کردن این صفحه، مراجعه به منوی Tools و گزینه‌ی Library Package Manager می‌باشد:



جهت دریافت به روز رسانی‌های بسته‌های نصب شده تنها کافی است به برگه‌ی Updates این صفحه مراجعه کرده و موارد لیست شده را نصب نمائیم:



نکته: NuGet در SharpDevelop 4.1 به بعد هم پشتیبانی می‌شود:




ب) با استفاده از امکانات خط فرمان PowerShell ویندوز
برای استفاده از امکانات پاورشل ویندوز نیاز است تا پاورشل نگارش 2 بر روی سیستم شما نصب باشد (نیاز به Windows XP with Service Pack 3 به بعد دارد). سپس به منوی Tools ، قسمت Library Package Manager ، گزینه‌ی Package Manager Console آن جهت فعال سازی کنسول پاور شل در VS.NET مراجعه نمائید:


نکته: در تصویر فوق پس از نوشتن el ، دکمه tab فشرده شده است. در این حالت منوی پکیج‌های مهیای شروع شده با el، از سایت مرکزی NuGet ظاهر گردیده است.

فرامین مهمی که در اینجا در دسترس هستند شامل: List-Package ، Uninstall-Package ، Update-Package و Get-Package می‌باشند. برای مثال اگر قصد جستجو در بین بسته‌های موجود را داشته باشید Get-Package بسیار مفید است:



برای مثال جهت یافتن بسته‌های مرتبط با wpf و silverlight به صورت زیر می‌توان عمل کرد:
PM> get-package -remote -filter wpf
PM> get-package -remote -filter silverlight

نکته: روش دیگر جستجو در بین بسته‌های مهیا، مراجعه به سایت گالری آن است : (+) . در اینجا دستور پاورشل نصب هر بسته‌ی یافت شده نیز نمایش داده می‌شود.


ج) استفاده از برنامه NuGet.exe
برنامه NuGet.exe از سایت CodePlex قابل دریافت است. این روش هم جهت علاقمندان به خط فرمان تدارک دیده شده است!
پس از دریافت آن فرض کنید می‌خواهیم تمام بسته‌های شروع شده با nhi را لیست کنیم. برای این منظور دستور خط فرمان ذیل را صادر کنید:
D:\Test>nuget list nhi
سپس برای دریافت مثلا NHibernate تنها کافی است دستور زیر اجرا شود:
D:\Test>nuget install NHibernate

به این صورت کتابخانه NHibernate ‌به همراه تمام وابستگی‌های آن دریافت خواهد شد.

به روز رسانی خودکار NuGet
برای به روز رسانی برنامه nuget.exe دستور زیر را می‌توان صادر نمود:
D:\Test>NuGet.exe u
و یا جهت فعال سازی به روز رسانی‌های خودکار افزونه‌ها در VS.NET به منوی زیر مراجعه کنید:
Tools | Options, then Environment | Extension Manager and click "Automatically check for updates to installed extensions."





ادامه دارد ...

مطالب
Query Options در پروتکل OData
در قسمت قبل  با OData به صورت مختصر آشنا شدیم. در این قسمت به امکانات توکار OData و جزئیات query options پرداخته و همچنین قابلیت‌های امنیتی این پروتکل را بررسی مینماییم.
در قسمت قبلی، config مربوط به OData و همچنین Controller و Crud مربوط به آن entity پیاده سازی شد. در این قسمت ابتدا سه موجودیت را به نام‌های Product ، Category و همچنین Supplier، به صورت زیر تعریف مینماییم:

به این صورت مدل‌های خود را تعریف کرده و طبق مقاله‌ی قبلی، Controller‌های هر یک را پیاده سازی نمایید:

public class Supplier
{
    [Key]
    public string Key {get; set; }
    public string Name { get; set; }
}
public class Category
{
    public int ID { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Product> Products { get; set; }
}

public class Product
{
    public int ID { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }

    [ForeignKey("Category")]
    public int CategoryId { get; set; }
    public Category Category { get; set; }

    [ForeignKey("Supplier")]
    public string SupplierId { get; set; }
    public virtual Supplier Supplier { get; set; }
}

پکیج Microsoft.AspNet.OData به تازگی ورژن 6 آن به صورت رسمی منتشر شده و شامل تغییراتی نسبت به نسخه‌ی قبلی آن است. اولین نکته‌ی حائز اهمیت، Config آن است که به صورت زیر تغییر کرده و باید Option‌های مورد نیاز، کانفیگ شوند. در این نسخه DI نیز به Odata اضافه شده است:

public static void Register(HttpConfiguration config)
        {
            ODataModelBuilder odataModelBuilder = new ODataConventionModelBuilder();

            var product = odataModelBuilder.EntitySet<Product>("Products");
            var category = odataModelBuilder.EntitySet<Category>("Categories");
            var supplier = odataModelBuilder.EntitySet<Supplier>("Suppliers");

            var edmModel = odataModelBuilder.GetEdmModel();

            supplier.EntityType.Ignore(c => c.Name);

            config.Select(System.Web.OData.Query.QueryOptionSetting.Allowed);
            config.MaxTop(25);
            config.OrderBy(System.Web.OData.Query.QueryOptionSetting.Allowed);
            config.Count(System.Web.OData.Query.QueryOptionSetting.Allowed);
            config.Expand(System.Web.OData.Query.QueryOptionSetting.Allowed);
            config.Filter(System.Web.OData.Query.QueryOptionSetting.Allowed);
            config.Count(System.Web.OData.Query.QueryOptionSetting.Allowed);

            //config.MapODataServiceRoute("ODataRoute", "odata", edmModel); // کانفیگ به صورت معمولی
           config.MapODataServiceRoute("ODataRoute", "odata",
                builder =>
                {
                    builder.AddService(ServiceLifetime.Singleton, sp => edmModel);
                    builder.AddService<IEnumerable<IODataRoutingConvention>>(ServiceLifetime.Singleton, sp => ODataRoutingConventions.CreateDefault());
                });
}
باید همه‌ی Query option‌هایی را که به آنها نیاز داریم، معرفی نماییم و فرض کنید که ProductsController و متد Get آن بدین صورت پیاده سازی شده باشد:
[EnableQuery]
        public IQueryable<Product> Get()
        {
            return new List<Product>
            {
                new Product { Id = 1, Name = "name 1", Price = 11, Category = new Category {Id =1, Name = "Cat1" } },
                new Product { Id = 2, Name = "name 2", Price = 12, Category = new Category {Id =2, Name = "Cat2" } },
                new Product { Id = 3, Name = "name 3", Price = 13, Category = new Category {Id =3, Name = "Cat3" } },
                new Product { Id = 4, Name = "name 4", Price = 14, Category = new Category {Id =4, Name = "Cat4" } },
            }.AsQueryable(); ;
        }
در این پروتکل به صورت توکار، optionهای زیر قابل استفاده است:

 Description Option 
 بسط دادن موجودیت مرتبط  $expand
 فیلتر کردن نتیجه، بر اساس شرط‌های Boolean ی  $filter
 فرمان به سرور که تعداد رکورد‌های بازگشتی را نیز نمایش دهد(مناسب برای پیاده سازی server-side pagging )  $count
 مرتب کردن نتیجه‌ی بازگشتی  $orderby
 select زدن روی پراپرتی‌های درخواستی  $select
 پرش کردن از اولین رکورد به اندازه‌ی n عدد  $skip
 فقط بازگرداندن n رکورد اول  $top
کوئری‌های زیر را در نظر بگیرید:

 در کوئری اول، فقط فیلد‌های Id,Name از Products برگشت داده خواهند شد و در کوئری دوم، از 2 رکورد اول، صرفنظر می‌شود و از بقیه‌ی آنها، فقط 3 رکورد بازگشت داده میشود:
/odata/Products?$select=Id,Name
/odata/Products?$top=3&$skip=2
/odata/Products?$count=true
در پاسخ کوئری فوق، تعداد رکورد‌های بازگشتی نیز نمایش داده میشوند:
{@odata.context: "http://localhost:4516/odata/$metadata#Products", @odata.count: 4,…}
@odata.context:"http://localhost:4516/odata/$metadata#Products"
@odata.count:4
value:[{Id: 1, Name: "name 1", Price: 11, SupplierId: 0, CategoryId: 0},…]
0:{Id: 1, Name: "name 1", Price: 11, SupplierId: 0, CategoryId: 0}
1:{Id: 2, Name: "name 2", Price: 12, SupplierId: 0, CategoryId: 0}
2:{Id: 3, Name: "name 3", Price: 13, SupplierId: 0, CategoryId: 0}
3:{Id: 4, Name: "name 4", Price: 14, SupplierId: 0, CategoryId: 0}
در response، این مقادیر به همراه تعداد رکورد بازگشتی، نمایش داده میشوند که برای پیاده سازی paging مناسب است.
/odata/Products?$filter=Id eq 1
در کوئری فوق eq مخفف equal و به معنای برابر است و بجای آن میتوان از gt به معنای بزرگتر و lt به معنای کوچکتر نیز استفاده کرد:
/odata/Products?$filter=Id gt 1 and Id lt 3
/odata/Products?$orderby=Id desc
در کوئری فوق نیز به صورت واضح، بر روی فیلد Id، مرتب سازی به صورت نزولی خواهد بود و در صورت وجود نداشتن کلمه کلیدی desc، به صورت صعودی خواهد بود.
بسط دادن موجودیت‌های دیگر نیز بدین شکل زیر میباشد:
/odata/Products?$expand=Category
برای اینکه چندین موجودیت دیگر نیز بسط داده شوند، اینگونه رفتار مینماییم:
/odata/Products?$expand=Category,Supplier
برای اینکه به صورت عمیق به موجودیت‌های دیگر بسط داده شود، بصورت زیر:
/odata/Categories(1)?$expand=Products/Supplier
و برای اینکه حداکثر تعداد رکورد بازگشتی را مشخص نماییم:
[EnableQuery(PageSize = 10)]


محدود کردن Query Options
به صورت زیر میتوانیم فقط option‌های دلخواه را فراخوانی نماییم. مثلا در اینجا فقط اجازه‌ی Skip و Top داده شده است و بطور مثال Select قابل فراخوانی نیست:
[EnableQuery (AllowedQueryOptions= AllowedQueryOptions.Skip | AllowedQueryOptions.Top)]
برای اینکه فقط اجازه‌ی logical function زیر را بدهیم (فقط eq):
[EnableQuery(AllowedLogicalOperators=AllowedLogicalOperators.Equal)]
برای اینکه Property خاصی در edm قابلیت نمایش نباشد، در config بطور مثال Price را ignore مینماییم:
var product = odataModelBuilder.EntitySet<Product>("Products");
product.EntityType.Ignore(e => e.Price);
برای اینکه فقط بر روی فیلد‌های خاصی بتوان از orderby استفاده نمود:
[EnableQuery(AllowedOrderByProperties = "Id,Name")]
یک option به نام value$ برای بازگرداندن تنها آن رکورد مورد نظر، به صورت مجزا میباشد. برای اینکار بطور مثال متد زیر را به کنترلر خود اضافه کنید:
public System.Web.Http.IHttpActionResult GetName(int key)
        {
            Product product = Get().Single(c => c.Id == key);            
            return Ok(product.Name);
        }
/odata/Products(1)/Name/$value
و حاصل کوئری فوق، مقداری بطور مثال برابر زیر خواهد بود و نه به صورت convention پاسخ‌های OData، فرمت بازگشتی "text/plain" خواهد بود و نه json:
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
DataServiceVersion: 3.0
Content-Length: 3

Ali

Attribute Convention
هایی هم برای اعتبارسنجی پراپرتی‌ها موجود است که نام آن‌ها واضح تعریف شده‌اند:
 Description  Attribute
 اجازه‌ی فیلتر زدن بر روی آن پراپرتی داده نخواهد شد  NotFilterable
 اجازه‌ی مرتب کردن بر روی آن پراپرتی داده نخواهد شد  NotSortable
 اجازه‌ی select زدن بر روی آن پراپرتی داده نمیشود  NotNavigable
 اجازه‌ی شمارش دهی بر روی آن Collection داده نمیشود  NotCountable
 اجازه‌ی بسط دادن آن Collection داده نمیشود  NotExpandable

و همچنین [AutoExpand] به صورت اتوماتیک آن موجودیت مورد نظر را بسط میدهد.

بطور مثال کد‌های زیر را در مدل خود میتوانید مشاهده نمائید:

public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }

        [NotFilterable, NotSortable]
        public decimal Price { get; set; }

        [ForeignKey(nameof(SupplierId))]
        [NotNavigable]
        public virtual Supplier Supplier { get; set; }
        public int SupplierId { get; set; }


        [ForeignKey(nameof(CategoryId))]
        public virtual Category Category { get; set; }
        public int CategoryId { get; set; }

        [NotExpandable]
        public virtual ICollection<TestEntity> TestEnities { get; set; }
    }

فرض کنید پراپرتی زیر را به مدل خود اضافه کرده اید

public DateTimeOffset CreatedOn { get; set; }

حال کوئری زیر را برای فیلتر زدن، بر روی آن در اختیار داریم:

/odata/Products?$filter=year(CreatedOn) eq 2016

در اینجا فقط Product هایی بازگردانده میشوند که در سال 2016 ثبت شده‌اند:

/odata/Products?$filter=CreatedOn lt cast(2017-04-01T04:11:31%2B08:00,Edm.DateTimeOffset)

کوئری فوق تاریخ مورد نظر را Cast کرده و همه‌ی Product هایی را که قبل از این تاریخ ثبت شده‌اند، باز می‌گرداند.

Nested Filter In Expand

/odata/Categories?$expand=Products($filter=Id gt 1 and Id lt 5)

همه‌ی Category‌ها به علاوه بسط دادن Product هایشان، در صورتیکه Id آنها بیشتر از 1 باشد

و یا حتی بر روی موجودیت بسط داده شده، select زده شود:

/odata/Categories?$expand=Products($select=Id,Name)


Custom Attribute

ضمنا به سادگی میتوان اتریبیوت سفارشی نوشت:

public class MyEnableQueryAttribute : EnableQueryAttribute
{
    public override IQueryable ApplyQuery(IQueryable queryable, ODataQueryOptions queryOptions)
    {
       // Don't apply Skip and Top.
       var ignoreQueryOptions = AllowedQueryOptions.Skip | AllowedQueryOptions.Top;
       return queryOptions.ApplyTo(queryable, ignoreQueryOptions);
    }
}

روی هر متدی از کنترلر خود که اتریبیوت [MyEnableQuery] را قرار دهید، دیگر قابلیت Skip, Top را نخواهد داشت.

Dependency Injection در آخرین نسخه‌ی OData اضافه شده است. بطور پیشفرض OData بصورت case-sensitive رفتار میکند. برای تغییر دادن آن در نسخه‌های قدیمی، Extension Methodی به نام EnableCaseSensitive وجود داشت. اما در نسخه‌ی جدید شما میتوانید پیاده سازی خاص خود را از هر کدام از بخش‌های OData داشته باشید و با استفاده از تزریق وابستگی، آن را به config برنامه‌ی خود اضافه کنید؛ برای مثال:

 public class CaseInsensitiveResolver : ODataUriResolver
    {
        private bool _enableCaseInsensitive;

        public override bool EnableCaseInsensitive
        {
            get { return true; }
            set { _enableCaseInsensitive = value; }
        }
    }

اینجا پیاده سازی از ODataUriResolver انجام شده و متد EnableCaseInsensitive به صورت جدیدی override و در حالت default مقدار true را برمیگرداند.

حال به صورت زیر آن را می‌توان به وابستگی‌های config برنامه، اضافه نمود:

config.MapODataServiceRoute("ODataRoute", "odata",
                builder =>
                {
                    builder.AddService(ServiceLifetime.Singleton, sp => edmModel);
                    builder.AddService<IEnumerable<IODataRoutingConvention>>(ServiceLifetime.Singleton, sp => ODataRoutingConventions.CreateDefault());
                    builder.AddService<ODataUriResolver>(ServiceLifetime.Singleton, sp => new CaseInsensitiveResolver()); // how enable case sensitive
                });

در قسمت بعدی به Action‌ها و Function‌ها در OData میپردازیم.

مطالب
معرفی Reactive extensions
Reactive extensions یا به صورت خلاصه Rx ،کتابخانه‌ی سورس باز تهیه شده‌ای توسط مایکروسافت است که اگر بخواهیم آن‌را به ساده‌ترین شکل ممکن تعریف کنیم، معنای Linq to events را می‌دهد و امکان مدیریت تعامل‌های پیچیده‌ی async را به صورت declaratively فراهم می‌کند. هدف آن بسط فضای نام System.Linq و تبدیل نتایج یک کوئری LINQ به یک مجموعه‌ی Observable است؛ به همراه مدیریت مسایل همزمانی آن.
این افزونه جزو موفق‌ترین کتابخانه‌های دات نتی مایکروسافت در سال‌های اخیر به شما می‌رود؛ تا حدی که معادل‌های بسیاری از آن برای زبان‌های دیگر مانند Java، JavaScript، Python، ‍CPP و غیره نیز تهیه شده‌اند.


استفاده از Rx به همراه یک کوئری LINQ

یک برنامه‌ی کنسول جدید را ایجاد کنید. سپس برای نصب کتابخانه‌ی Rx، دستور ذیل را در کنسول پاورشل نیوگت اجرا نمائید:
 PM> Install-Package Rx-Main
نصب آن از طریق نیوگت، به صورت خودکار کلیه وابستگی‌های مرتبط با آن‌را نیز به پروژه‌ی جاری اضافه می‌کند:
<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Rx-Core" version="2.2.4" targetFramework="net45" />
  <package id="Rx-Interfaces" version="2.2.4" targetFramework="net45" />
  <package id="Rx-Linq" version="2.2.4" targetFramework="net45" />
  <package id="Rx-Main" version="2.2.4" targetFramework="net45" />
  <package id="Rx-PlatformServices" version="2.2.4" targetFramework="net45" />
</packages>
سپس متد Main این برنامه را به نحو ذیل تغییر دهید:
using System;
using System.Linq;

namespace Rx01
{
    class Program
    {
        static void Main(string[] args)
        {
            var query = Enumerable.Range(1, 5).Select(number => number);
            foreach (var number in query)
            {
                Console.WriteLine(number);
            }
            finished();
        }

        private static void finished()
        {
            Console.WriteLine("Done!");
        }
    }
}
در اینجا یک سری عملیات متداول را مشاهده می‌کنید. بازه‌ای از اعداد توسط متد Enumerable.Range ایجاد شده و سپس به کمک یک حلقه‌، تمام آیتم‌های آن نمایش داده می‌شوند. همچنین در پایان کار نیز یک متد دیگر فراخوانی شده‌است.
اکنون اگر بخواهیم همین عملیات را توسط Rx انجام دهیم، به شکل زیر خواهد بود:
using System;
using System.Linq;
using System.Reactive.Linq;

namespace Rx01
{
    class Program
    {
        static void Main(string[] args)
        {
            var query = Enumerable.Range(1, 5).Select(number => number);
            var observableQuery = query.ToObservable();
            observableQuery.Subscribe(onNext: number => Console.WriteLine(number), onCompleted: () => finished());
        }

        private static void finished()
        {
            Console.WriteLine("Done!");
        }
    }
}
ابتدا نیاز است تا کوئری متداول LINQ را تبدیل به نمونه‌ی Observable آن کرد. اینکار را توسط متد الحاقی ToObservable که در فضای نام System.Reactive.Linq تعریف شده‌است، انجام می‌دهیم. به این ترتیب، هر زمانیکه که عددی به query اضافه می‌شود، با استفاده از متد Subscribe می‌توان تغییرات آن‌را تحت کنترل قرار داد. برای مثال در اینجا هربار که عددی در بازه‌ی 1 تا 5 تولید می‌شود، یکبار پارامتر onNext اجرا خواهد شد. برای نمونه در مثال فوق، از نتیجه‌ی آن برای نمایش مقدار دریافتی، استفاده شده‌است. سپس توسط پارامتر اختیاری onCompleted، در پایان کار، یک متد خاص را می‌توان فراخوانی کرد. خروجی برنامه در این حالت نیز به صورت ذیل است:
1
2
3
4
5
Done!
البته اگر قصد خلاصه نویسی داشته باشیم، سطر آخر متد Main، با سطر ذیل یکی است:
 observableQuery.Subscribe(Console.WriteLine, finished);

در این مثال ساده صرفا یک Syntax دیگر را نسبت به حلقه‌ی foreach متداول مشاهده کردیم که اندکی فشرده‌تر است. در هر دو حالت نیز عملیات انجام شده در تردجاری صورت گرفته‌اند. اما قابلیت‌ها و ارزش‌های واقعی Rx زمانی آشکار خواهند شد که پردازش موازی و پردازش در تردهای دیگر را در آن فعال کنیم.


الگوی Observer

Rx پیاده سازی کننده‌ی الگوی طراحی شیءگرایی به نام Observer است. برای توضیح آن یک لامپ و سوئیچ برق را درنظر بگیرید. زمانیکه لامپ مشاهده می‌کند سوئیچ برق در حالت روشن قرار گرفته‌است، روشن خواهد شد و برعکس. در اینجا به سوئیچ، subject و به لامپ، observer گفته می‌شود. هر زمان که حالت سوئیچ تغییر می‌کند، از طریق یک callback، وضعیت خود را به observer اعلام خواهد کرد. علت استفاده از callbackها، ارائه راه‌حل‌های عمومی است تا بتواند با انواع و اقسام اشیاء کار کند. به این ترتیب هر بار که شیء observer از نوع متفاوتی تعریف می‌شود (مثلا بجای لامپ یک خودرو قرار گیرد)، نیازی نخواهد بود تا subject را تغییر داد.
در Rx دو اینترفیس معادل observer و subject تعریف شده‌اند. در اینجا اینترفیس IObserver معادل observer است و اینترفیس IObservable معادل subject می‌باشد:
    class Subject : IObservable<int>
    {
        public IDisposable Subscribe(IObserver<int> observer)
        {
        }
    }
کار متد Subscribe، اتصال به Observer است و برای این حالت نیاز به کلاسی دارد که اینترفیس IObserver را پیاده سازی کند.
    class Observer : IObserver<int>
    {
        public void OnCompleted()
        {
        }

        public void OnError(Exception error)
        {
        }

        public void OnNext(int value)
        {
        }
    }
در اینجا OnCompleted زمانی اجرا می‌شود که پردازش مجموعه‌ای از اعداد int پایان یافته باشد. OnError در زمان وقوع استثنایی اجرا می‌شود و OnNext به ازای هر عدد موجود در مجموعه‌ی در حال پردازش، یکبار اجرا می‌شود. البته نیازی به پیاده سازی صریح این اینترفیس نیست و توسط متد توکار Observer.Create می‌توان به همین نتیجه رسید.
مجموعه‌های Observable کلید کار با Rx هستند. در مثال قبل ملاحظه کردیم که با استفاده از متد الحاقی ToObservable بر روی یک کوئری LINQ و یا هر نوع IEnumerable ایی،  می‌توان یک مجموعه‌ی Observable را ایجاد کرد. خروجی کوئری حاصل از آن به صورت خودکار اینترفیس IObservable را پیاده سازی می‌کند که دارای یک متد به نام Subscribe است.
در متد Subscribe کاری که به صورت خودکار صورت خواهد گرفت، ایجاد یک حلقه‌ی foreach بر روی مجموعه‌ی مورد آنالیز و سپس فراخوانی متد OnNext کلاس پیاده سازی کننده‌ی IObserver به ازای هر آیتم موجود در مجموعه است (فراخوانی observer.OnNext). در پایان کار هم فقط return this در اینجا صورت خواهد گرفت. در حین پردازش حلقه، اگر خطایی رخ دهد، متد observer.OnError انجام می‌شود.

در مثال قبل،کوئری LINQ نوشته شده، خروجی از نوع IObservable ندارد. به کمک متد الحاقی ToObservable:
public static System.IObservable<TSource> ToObservable<TSource>(
    this System.Collections.Generic.IEnumerable<TSource> source,
    System.Reactive.Concurrency.IScheduler scheduler)
به صورت خودکار، IEnumerable حاصل از کوئری LINQ را تبدیل به یک IObservable کرده‌ایم. به این ترتیب اکنون کوئری LINQ ما همانند سوئیچ برق عمل می‌کند و با تغییر آیتم‌های موجود در آن، مشاهده‌گرهایی که به آن متصل شده‌اند (از طریق فراخوانی متد Subscribe)، امکان دریافت سیگنال‌های تغییر وضعیت آن‌را خواهند داشت.
البته استفاده از متد Subscribe به نحوی که در مثال قبل ذکر شد، خلاصه شده‌ی الگوی Observer است. اگر بخواهیم دقیقا مانند الگو عمل کنیم، چنین شکلی را خواهد داشت:
 var query = Enumerable.Range(1, 5).Select(number => number);
var observableQuery = query.ToObservable();
var observer = Observer.Create<int>(onNext: number => Console.WriteLine(number));
observableQuery.Subscribe(observer);
ابتدا توسط متد ToObservable یک IObservable (سوئیچ) را ایجاد کرده‌ایم. سپس توسط کلاس Observer موجود در فضای نام System.Reactive، یک IObserver (لامپ) را ایجاد کرده‌ایم. کار اتصال سوئیچ به لامپ در متد Subscribe انجام می‌شود. اکنون هر زمانیکه تغییری در وضعیت observableQuery حاصل شود، سیگنالی را به observer ارسال می‌کند. در اینجا callbacks کار مدیریت observer را انجام می‌دهند.


پردازش نتایج یک کوئری LINQ در تردی دیگر توسط Rx

برای اجرای نتایج متد Subscribe در یک ترد جدید، می‌توان پارامتر scheduler متد ToObservable را مقدار دهی کرد:
using System;
using System.Linq;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Threading;

namespace Rx01
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Thread-Id: {0}", Thread.CurrentThread.ManagedThreadId);
            var query = Enumerable.Range(1, 5).Select(number => number);
            var observableQuery = query.ToObservable(scheduler: NewThreadScheduler.Default);
            observableQuery.Subscribe(onNext: number =>
            {
                Console.WriteLine("number: {0}, on Thread-id: {1}", number, Thread.CurrentThread.ManagedThreadId);
            }, onCompleted: () => finished());
        }

        private static void finished()
        {
            Console.WriteLine("Done!");
        }
    }
}
خروجی این مثال به نحو ذیل است:
 Thread-Id: 1
number: 1, on Thread-id: 3
number: 2, on Thread-id: 3
number: 3, on Thread-id: 3
number: 4, on Thread-id: 3
number: 5, on Thread-id: 3
Done!
پیش از آغاز کار و در متد Main، ترد آی دی ثبت شده مساوی 1 است. سپس هربار که callback متد Subscribe فراخوانی شده‌است، ملاحظه می‌کنید که ترد آی دی آن مساوی عدد 3 است. به این معنا که کلیه نتایج در یک ترد مشخص دیگر پردازش شده‌اند.
NewThreadScheduler.Default در فضای نام System.Reactive.Concurrency واقع شده‌است.


یک نکته
در نگارش‌های آغازین Rx، مقدار scheduler را می‌شد معادل Scheduler.NewThread نیز قرار داد که در نگارش‌های جدید منسوخ شده درنظر گرفته شده و به زودی حذف خواهد شد. معادل‌های جدید آن اکنون NewThreadScheduler.Default، ThreadPoolScheduler.Default و امثال آن هستند.


مدیریت خاتمه‌ی اعمال انجام شده‌ی در تردهای دیگر توسط Rx

یکی از مواردی که حین اجرای نتیجه‌ی callbackهای پردازش شده‌ی در تردهای دیگر نیاز است بدانیم، زمان خاتمه‌ی کار آن‌ها است. برای نمونه در مثال قبل، نمایش Done پس از پایان تمام callbacks انجام شده‌است. فرض کنید، callback پایان عملیات را حذف کرده و متد finished را پس از فراخوانی متد observableQuery.Subscribe قرار دهیم:
observableQuery.Subscribe(onNext: number =>
{
   Console.WriteLine("number: {0}, on Thread-id: {1}", number,     
                              Thread.CurrentThread.ManagedThreadId);
}/*, onCompleted: () => finished()*/);
finished();
اینبار اگر برنامه را اجرا کنیم به خروجی ذیل خواهیم رسید:
 Thread-Id: 1
number: 1, on Thread-id: 3
Done!
number: 2, on Thread-id: 3
number: 3, on Thread-id: 3
number: 4, on Thread-id: 3
number: 5, on Thread-id: 3
این خروجی بدین معنا است که متد  observableQuery.Subscribeدر حین اجرا شدن در تردی دیگر، صبر نخواهد کرد تا عملیات مرتبط با آن خاتمه یابد و سپس سطر بعدی را اجرا کند. بنابراین برای حل این مشکل، تنها کافی است به آن اعلام کنیم که پس از پایان عملیات، onCompleted را اجرا کن.


مدیریت استثناهای رخ داده در حین پردازش مجموعه‌های واکنشگرا

متد Subscribe دارای چندین overload است. تا اینجا نمونه‌ای که دارای پارامترهای onNext و onCompleted بودند را بررسی کردیم. اگر بخواهیم مدیریت استثناءها را نیز در اینجا اضافه کنیم، فقط کافی است از overload دیگر آن که دارای پارامتر onError است، استفاده نمائیم:
observableQuery.Subscribe(
  onNext: number => Console.WriteLine(number),
  onError: exception => Console.WriteLine(exception.Message),
  onCompleted: () => finished());
اگر callback پارامتر onError اجرا شود، دیگر به onCompleted نخواهیم رسید. همچنین دیگر onNext ایی نیز اجرا نخواهد شد.


مدیریت ترد اجرای نتایج حاصل از Rx در یک برنامه‌ی دسکتاپ WPF یا WinForms

تا اینجا مشاهده کردیم که اجرای callbackهای observer در یک ترد دیگر، به سادگی تنظیم پارامتر scheduler متد ToObservable است. اما در برنامه‌های دسکتاپ برای به روز رسانی عناصر رابط کاربری، حتما باید در تردی قرار داشته باشیم که آن رابط کاربری در آن ایجاد شده‌است یا به عبارتی در ترد اصلی برنامه؛ در غیر اینصورت برنامه کرش خواهد کرد. مدیریت این مساله نیز در Rx بسیار ساده‌است. ابتدا نیاز است بسته‌ی Rx-WPF را نصب کرد:
 PM> Install-Package Rx-WPF
سپس توسط متد ObserveOn می‌توان مشخص کرد که نتیجه‌ی عملیات باید بر روی کدام ترد اجرا شود:
 observableQuery.ObserveOn(DispatcherScheduler.Current).Subscribe(...)
روش دیگر آن استفاده از متد ObserveOnDispatcher می‌باشد:
 observableQuery.ObserveOnDispatcher().Subscribe(...)
بنابراین مشخص سازی پارامتر scheduler متد ToObservable، به معنای اجرای query آن در یک ترد دیگر و استفاده از متد ObserveOn، به معنای مشخص سازی ترد اجرای callbackهای مشاهده‌گر است.

و یا اگر از WinForms استفاده می‌کنید، ابتدا بسته‌ی Rx خاص آن‌را نصب کنید:
 PM> Install-Package Rx-WinForms
و سپس ترد اجرای callbackها را SynchronizationContext.Current مشخص نمائید:
 observableQuery.ObserveOn(SynchronizationContext.Current).Subscribe(...)

یک نکته‌
در Rx فرض می‌شود که کوئری شما زمانبر است و callbackهای مشاهده‌گر سریع عمل می‌کنند. بنابراین هدف از callbackهای آن، پردازش‌های سنگین نیست. جهت آزمایش این مساله، اینبار query ابتدایی برنامه را به شکل ذیل تغییر دهید که در آن بازگشت زمانبر یک سری داده شبیه سازی شده‌اند.
 var query = Enumerable.Range(1, 5).Select(number =>
{
   Thread.Sleep(250);
   return number;
});
سپس با استفاده از متد ToObservable، ترد دیگری را برای اجرای واقعی آن مشخص کنید تا در حین اجرای آن برنامه در حالت هنگ به نظر نرسد و سپس نمایش آن‌را به کمک متد ObserveOn، بر روی ترد اصلی برنامه انجام دهید.
مطالب
React 16x - قسمت 1 - معرفی و شروع به کار
React یک کتابخانه‌ی جاوا اسکریپتی، برای ساخت رابط‌های کاربری سریع و تعاملی است. توسعه‌ی آن از سال 2011 در فیسبوک شروع شد و در حال حاضر محبوب‌ترین کتابخانه‌ی جاوا اسکریپتی در این رده‌است:


به همین جهت اگر می‌خواهید رزومه‌ی غنی‌تری را ارائه دهید، فراگیری React می‌تواند موقعیت‌های شغلی بیشتری را نصیب شما کند.


ساختار کلی یک برنامه‌ی React

کامپوننت‌ها (جزئی از یک رابط کاربری) قلب هر برنامه‌ی React ای را تشکیل می‌دهند. برای ساخت یک برنامه‌ی React، تعدادی کامپوننت مستقل را تهیه و با هم ترکیب می‌کنیم تا به رابط کاربری نهایی برسیم.
هر برنامه‌ی React، حداقل از یک کامپوننت تشکیل می‌شود که به آن Root component هم می‌گویند. این کامپوننت بیانگر کل برنامه‌است و دربرگیرنده‌ی مابقی Child components برنامه است. بنابراین ساختار هر برنامه‌ی React، شبیه به درختی از کامپوننت‌ها است. اگر با Angular 2 به بعد کار کرده باشید، این مفهوم برای شما آشنا است.
یک مثال: فرض کنید می‌خواهیم UI برنامه‌ای را به مانند رابط کاربری Twitter، ایجاد کنیم. هر قسمت یک صفحه‌ی توئیتر، به کامپوننت‌هایی شکسته می‌شود؛ مانند منوی راهبری، نمایش پروفایل شخص، نمایش لیست آخرین اخبار مورد علاقه‌ی شخص و نمایش فید. اگر بخواهیم این ساختار را توسط یک برنامه‌ی React شبیه سازی کنیم، در بالاترین سطح، کامپوننت root را خواهیم داشت که کار ترکیب و نمایش سایر کامپوننت‌های برنامه مانند nav bar ، trends ، profile و feed را انجام می‌دهد. اکنون در این ساختار ایجاد شده، برای مثال کامپوننت feed نیز می‌تواند از چندین کامپوننت مجزا تشکیل شود؛ مانند کامپوننت‌های tweet و like.
بنابراین هر کامپوننت، قسمتی از UI را تشکیل می‌دهد. هر کدام از آن‌ها به صورت مجزای از دیگری ساخته شده و سپس در کنار هم قرار می‌گیرند تا UI نهایی را شکل دهند:



هر کامپوننت در React به صورت یک کلاس ES6، با ساختاری که دارای یک شیء state و متد render است، تشکیل می‌شود:
class Tweet {
 state = {};
 
 render() {
 } 
}
state در اینجا همان اطلاعاتی است که قرار است در زمان نمایش این کامپوننت، رندر شود. کار متد render نیز همانطور که از نام آن نیز مشخص است، بیان نحوه‌ی تشکیل و رندر UI است. خروجی این متد، یک React Element است که در حقیقت یک شیء جاوا اسکریپتی خالص است و در نهایت به المان‌های DOM، نگاشت می‌شود. یک React Element، یک DOM Element واقعی نیست؛ بلکه تنها یک شیء جاوا اسکریپتی بیانگر DOM Element، در حافظه‌است. بنابراین یک برنامه‌ی React تشکیل شده‌است از لیستی از React Elementها در حافظه که به آن Virtual DOM هم گفته می‌شود.
مزیت کارکردن با Virtual DOM، سادگی ایجاد، تغییر و به روز رسانی آن در مقایسه با DOM واقعی است که در نهایت کار رندر عناصر UI را در مرورگر انجام می‌دهد. زمانیکه در state کامپوننتی تغییری رخ می‌دهد، یک React Element جدید تولید می‌شود. سپس React این شیء جدید را با نمونه‌ی قبلی آن مقایسه کرده و تغییرات رخ‌داده را محاسبه می‌کند. در آخر این تغییرات را به DOM واقعی اعمال می‌کند تا با Virtual DOM موجود هماهنگ شود.
بنابراین در حین کار با React، دیگر همانند کار با جاوا اسکریپت خالص و یا jQuery، مستقیما عناصر UI و DOM واقعی را تغییر نمی‌دهیم. در اینجا فقط state یک کامپوننت را تغییر می‌دهیم و سپس React، کار ایجاد شیء UI درون حافظه‌ای متناظر با آن و سپس اعمال آن‌را به UI نهایی قابل مشاهده‌ی در مرورگر، انجام می‌دهد. به همین جهت به این کتابخانه React می‌گویند! چون به تغییرات state کامپوننت‌ها واکنش نشان می‌دهد و سپس DOM واقعی را به روز می‌کند.


Angular یا React؟!

هر دوی React و Angular از لحاظ طراحی کامپوننت‌ها بسیار شبیه به هم هستند؛ اما Angular یک فریم‌ورک است و React تنها یک کتابخانه. تنها کاری را که React انجام می‌دهد، رندر View است و هماهنگ نگه داشتن آن با state کامپوننت‌ها. این تمام کاری است که React انجام می‌دهد؛ نه بیشتر و نه کمتر! بنابراین یادگیری React، بسیار سریع‌تر و ساده‌تر از Angular است. بدیهی است یک برنامه‌ی تک صفحه‌ای وب، از اجزای دیگری مانند مسیریابی و یا کار با سرویس‌های HTTP نیز تشکیل می‌شود. در React شما مختار هستید که کتابخانه‌های جانبی فراهم شده‌ی برای آن‌را خودتان انتخاب کرده و استفاده کنید؛ برخلاف روشی که در Angular مرسوم است و به صورت مشخص و ثابتی به همراه این فریم‌ورک ارائه می‌شوند.


برپایی محیط توسعه‌ی React

اولین برنامه‌ای را که برای کار با React باید نصب کنید، node.js است. البته ما در این سری قرار نیست با node.js کار کنیم؛ اما از یکی از اجزای آن به نام node package manager یا npm، برای نصب کتابخانه‌ی جاوا اسکریپتی ثالث، زیاد استفاده خواهیم کرد. پس از نصب آن، به خط فرمان مراجعه کرد و دستور زیر را صادر کنید:
> npm install -g npm@latest
این دستور npm قدیمی موجود بر روی سیستم را به روز رسانی می‌کند (اگر پیشتر یک node.js قدیمی را نصب و اکنون آن‌را به روز رسانی کرده‌اید).

اگر هم خیلی پیشترها node.js را نصب کرده‌اید (برای مثال چند سال قبل!)، نصب نگارش جدید آن احتمالا کار نخواهد کرد. حتی عزل و نصب مجدد آن نیز کارساز نیست. در این حالت باید پس از عزل آن، پوشه‌های قدیمی آن‌را یکی یکی یافته و دستی حذف کنید . سپس مجددا آن‌را نصب کنید.

در ادامه در خط فرمان و توسط npm، قالب create-react-app را نصب خواهیم کرد:
> npm i -g create-react-app
در اینجا سوئیچ i به معنای install است و g یعنی نصب global و سراسری بسته‌ی create-react-app. نصب سراسری یک بسته یعنی در هر پوشه‌ای می‌توان به امکانات آن دسترسی یافت و از آن استفاده کرد. اگر از سوئیچ g استفاده نمی‌شد، این بسته تنها در پوشه‌ی جاری و با سطح دید مختص به آن، نصب و قابل استفاده می‌شد.

ابزار دیگری که در این سری از آن استفاده خواهیم کرد، ادیتور بسیار معروف و محبوب VSCode است. پس از دریافت و نصب آن، چند افزونه‌ی زیر را نیز به آن اضافه خواهیم کرد:
برای نصب آن‌ها، پنل extensions را در VSCode، از نوار ابزار کنار صفحه‌ی آن، انتخاب کرده و نام‌های فوق را در آن جستجو و سپس نصب کنید.

و یا می‌توانید این فایل را اجرا کرده و تعدادی از افزونه‌های مفید VSCode را یکجا نصب کنید: install-addons.zip

همچنین قابلیت فرمت‌کردن پس از Save را نیز در VSCode فعال کنید تا پس از هربار Save، اعمال این افزونه‌ها به صورت خودکار صورت گیرد. برای این منظور گزینه‌ی file->preferences->settings را در VSCode انتخاب کرده و سپس save را جستجو کرده و Format On Save را انتخاب کنید:


علاوه بر این‌ها، جهت کار بهتر با VSCode، بهتر است بررسی کننده‌های کدهای جاوا اسکریپتی (static code analyzers) را نیز با اجرای دستور زیر نصب کنید:
> npm i -g typescript eslint tslint eslint-plugin-react-hooks

پس از این تغییرات، نیاز است یکبار VSCode را بسته و مجددا باز کنید. سپس مجددا گزینه‌ی file->preferences->settings را در VSCode انتخاب کرده و ابتدا eslint را در اینجا جستجو کنید. در صفحه‌ی نمایش تنظیمات آن، گزینه‌ی Auto fix on save آن‌را انتخاب نمائید. در آخر در همین قسمت settings، عبارت prettier را انتخاب کنید. در اینجا اگر گزینه‌ی قدیمی یکپارچگی با eslint آن هنوز وجود دارد، آن‌را از حالت انتخاب شده خارج کنید (به صورت قرمز و deprecated نمایش داده می‌شود) تا افزونه‌ی prettier بدون مشکل و خطا کار کند (disable Prettier ESLint integration).


ایجاد قالب اولین برنامه‌ی React

در ادامه برای ایجاد اولین برنامه‌ی React، از بسته‌ی create-react-app که پیشتر آن‌را نصب کردیم، استفاده می‌کنیم. برای این منظور در خط فرمان دستور زیر را صادر کنید:
> create-react-app sample-01
در اینجا sample-01 یک نام دلخواه است و در حین اجرای این دستور باید به اینترنت متصل باشید تا وابستگی‌های مرتبط با پروژه را نیز دریافت کند. برای بار اول، اجرای آن ممکن است کمی طول بکشد. اما از دفعات آتی، چون بسته‌های مرتبط را در npm-cache سیستم نیز ذخیره می‌کند، اجرای آن بسیار سریع خواهد بود.
این قالب نه تنها React را نصب می‌کند، بلکه یک development server را برای اجرا و مشاهده‌ی سریع برنامه، webpack را برای یکی کردن فایل‌ها (bundling & minification)، Babel را برای کامپایل کدهای فایل‌های JSX و ... نیز نصب می‌کند. بنابراین به این ترتیب، یک پروژه‌ی تنظیم شده و آماده‌ی استفاده و توسعه را شاهد خواهیم بود که نیازی به تنظیمات اولیه‌ی آن نیست.
پس ایجاد برنامه، وارد پوشه‌ی sample-01 شده و دستور npm start را صادر کنید:
> cd sample-01
> npm start
به این ترتیب برنامه بر روی پورت 3000، قابل دسترسی و مشاهده می‌شود:


development server آن، تغییرات فایل‌های برنامه را تحت نظر قرار می‌دهد و با هر تغییری، به صورت خودکار برنامه را در مرورگر بارگذاری مجدد خواهد کرد.


بررسی ساختار اولین پروژه‌ی React ایجاد شده

ساختار پوشه‌ها و فایل‌های مثال اولیه‌ی ایجاد شده توسط قالب create-react-app به صورت زیر است:


البته شما در این تصویر پوشه‌ی node_modules را که در کنار این پوشه‌ها قرار دارد، مشاهده نمی‌کنید. وجود یک چنین پوشه‌ی سنگینی با هزاران فایل داخل آن، کار نمایشی IDEها را با مشکل مواجه می‌کند (مصرف حافظه‌ی بالا، به همراه کند شدن شدید آن). اگر نمی‌خواهید این پوشه نمایش داده شود، در مسیر file->preferences->settings، عبارت npm را جستجو کرده و سپس در قسمت npm: exclude آن، بر روی لینک edit in settings.json کلیک کنید:


 و سپس در فایل باز شده، یک چنین تنظیمی را می‌توانید اضافه و یا ویرایش و تکمیل کنید:
  "files.exclude": {
    "**/.git": true,
    "**/.svn": true,
    "**/.hg": true,
    "**/CVS": true,
    "**/.DS_Store": true,
    "**/node_modules": true,
    "**/wwwroot": true,
    "**/bower_components": true,
    "**/**/bin": true,
    "**/**/obj": true,
    "**/packages": true
  },

در ادامه پوشه‌ی public این پروژه را مشاهده می‌کنید. تمام فایل‌هایی که قرار است به صورت عمومی توسط برنامه ارائه شوند، مانند favicon.ico و غیره، در این پوشه قرار می‌گیرند.
در این پوشه بر روی فایل index.html آن کلیک کنید تا بتوان محتوای آن‌را بهتر بررسی کرد. برای مثال در ابتدای آن، درج تعدادی متادیتا را که یکی از آن‌ها ذکر manifest.json است، مشاهده می‌کنید. کار فایل manifest.json، ارائه‌ی یک سری متادیتای خاص مخصوص دستگاه‌های موبایل است که در آن‌ها بجای favicon.ico، می‌توان از تصاویر و یا آیکن‌های بزرگتری مانند فایل‌های png موجود در پوشه‌ی public، استفاده کرد. در ادامه‌ی این فایل، به تنظیم زیر می‌رسیم:
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
div با id مساوی root، محل ارائه‌ی کل برنامه‌ی React ما است.

در پوشه‌ی src و فایل App.js آن، شاهد یک کامپوننت ابتدایی هستید که کار رندر صفحه‌ی مشکی پیش‌فرض این قالب را انجام می‌دهد. در این فایل، شاهد بازگشت یک چنین تگ‌هایی هستیم:
  return (
    <div className="App">
      <header className="App-header">
       ... 
      </header>
    </div>
  );
احتمالا تابحال چنین return ای را در برنامه‌های جاوا اسکریپتی مشاهده نکرده‌اید؛ چون درج آن‌ها در فایل‌های js به این نحو، غیرمجاز است. این تگ‌ها نه رشته‌ای هستند و نه HTML خالص. به آن jsx گفته می‌شود که مخفف JavaScript XML می‌باشد. کار آن ارائه‌ی ساختار UI ای است که قرار است رندر شود. یک چنین کدی برای اینکه قابل تفسیر و اجرا باشد، از درون کامپایلر ویژه‌ای به نام Babel عبور می‌کند و تبدیل به کدهای جاوا اسکریپتی خالصی می‌شود که برای مرورگرها قابل درک و اجرا است.
برای درک بهتر آن به آدرس https://babeljs.io/repl مراجعه کنید. سپس در سمت چپ صفحه، یک قطعه کد jsx را به یک ثابت انتساب دهید:
const element = <h1>Hello World!</h1>;


همانطور که مشاهده می‌کنید، این قطعه کد jsx (که یک رشته‌ی معمولی نیست)، توسط Babel به یک قطعه کد کاملا جاوا اسکریپتی قابل درک برای مرورگر تبدیل شده‌است:
"use strict";

var element = React.createElement("h1", null, "Hello World!");

بدیهی است نوشتن کدهای jsx، ساده‌تر از نوشتن قطعه کد فوق است و درک آن نیز به علت شباهت آن به HTML، آسان‌تر است. به همین جهت در کدهای React، ما از jsx استفاده می‌کنیم و تفسیر آن‌را به Babel واگذار خواهیم کرد.

در پوشه‌ی src، فایل مهم دیگری که وجود دارد، index.js است. این فایل نقطه‌ی آغازین برنامه را مشخص می‌کند. در قسمت‌های بعدی، محتویات این فایل را بیشتر بررسی خواهیم کرد.
در اینجا فایل serviceWorker.js را نیز مشاهده می‌کنید. این فایل به صورت خودکار توسط قالب create-react-app ایجاد شده‌است و کار آن کمک به ارائه‌ی محلی برنامه، توسط development server آن است. بنابراین ما کاری با این فایل نخواهیم داشت.


نوشتن اولین برنامه‌ی React

به پوشه‌ی src ایجاد شده مراجعه کرده و تمام فایل‌های موجود و پیش‌فرض آن‌را حذف کنید. در ادامه خودمان آن‌ها را از صفر ایجاد خواهیم کرد. برای این منظور فایل جدید و خالی src\index.js را ایجاد می‌کنیم. در ابتدای کار نیاز است تعدادی ماژول React را import کنیم.
import React from "react";

const element = <h1>Hello World!</h1>;
console.log(element);
در اینجا شیء React از ماژول react دریافت شده و سپس یک ثابت را با یک عبارت jsx مقدار دهی کرده‌ایم. چون از jsx استفاده می‌کنیم، ذکر import ابتدای فایل الزامی است؛ از این جهت که Babel به کمک آن است که می‌تواند معادل React.createElement را تولید کند.
اگر هنوز برنامه توسط دستور npm start در حال اجرا است، هر بار که فایل index.js را ذخیره می‌کنیم، خروجی نهایی را در مرورگر نمایش می‌دهد (اگر هم آن‌را بسته‌اید، یکبار از طریق خط فرمان، دستور npm start را در ریشه‌ی پروژه، صادر کنید). به این قابلیت hot module reloading هم گفته می‌شود.
در این حالت اگر به مرورگر مراجعه کنید، یک صفحه‌ی سفید را مشاهده خواهید کرد. اکنون دکمه‌ی F12 را فشرده (و یا ctrl+shift+i) و developer console مرورگر را باز کنید.


شیءای را که در اینجا مشاهده می‌کنید، همان حاصل console.log کدهای فوق است؛ به عبارتی Babel، عبارت jsx ما را تبدیل به یک شیء جاوا اسکریپتی قابل فهم برای مرورگر کرده‌است که از دیدگاه React، جزئی از همان Virtual DOM ای است که پیشتر معرفی شد (نمایش درون حافظه‌ای DOM مختص React، جهت محاسبه‌ی تغییرات، با تغییر state هر کامپوننت و سپس اعمال آن‌ها به DOM اصلی در مرورگر).
اکنون می‌خواهیم این المان را در DOM اصلی، رندر کرده و نمایش دهیم:
import React from "react";
import ReactDOM from "react-dom";

const element = <h1>Hello World!</h1>;
console.log(element);

ReactDOM.render(element, document.getElementById("root"));
برای این منظور نیاز است از متد ReactDOM.render استفاده کرد. این شیء در ماژول react-dom قرار دارد؛ به همین جهت در ابتدای فایل import شده‌است. سپس در متد render آن، ابتدا المانی که قرار است رندر شود ذکر خواهد شد و سپس محل درج آن‌را مشخص می‌کنیم که دقیقا به همان div با id مساوی root در فایل public\index.html اشاره می‌کند.
اکنون پس از ذخیره سازی فایل index.js، اگر به مرورگر مراجعه کنید، عبارت Hello World! را مشاهده خواهید کرد:


همانطور که در این تصویر نیز مشخص است، المان h1 ما را داخل div ای با id مساوی root، درج کرده‌است.

هدف از این مثال ساده، نمایش نحوه‌ی کارکرد React، در پشت صحنه بود. در یک برنامه‌ی واقعی، بجای رندر یک المان ساده در DOM، کار رندر App component را انجام خواهیم داد. کامپوننت App، کامپوننت ریشه‌ای برنامه بوده و می‌تواند شامل درختی از کامپوننت‌ها که UI نهایی را تشکیل می‌دهند، شود.


نگاهی به تنظیمات پروژه‌ی ایجاد شده

اگر فایل package.json پروژه را باز کنید، یک چنین بسته‌هایی در آن درج شده‌است:
{
  "name": "sample-01",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "react": "^16.11.0",
    "react-dom": "^16.11.0",
    "react-scripts": "3.2.0"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}
در اینجا صرفا سه بسته‌ی react، react-dom و react-scripts را در قسمت dependencies مشاهده می‌کنید که کل Importهای ما را تشکیل می‌دهند.
بسته‌ی react-scripts است که کار مدیریت چهار جزء قسمت scripts این فایل را انجام می‌دهد. برای نمونه دستور npm start ای که در اینجا تعریف شده، سبب اجرای react-scripts start می‌شود. در ادامه اگر دستور npm run build را اجرا کنیم، یک بسته‌ی نهایی بهینه سازی شده را تولید می‌کند.
آخرین دستور آن eject است. اگر دستور npm run eject را اجرا کنید، امکان سفارشی سازی پشت صحنه‌ی create-react-app را خواهید داشت؛ اما در نهایت به یک فایل package.json بسیار شلوغ خواهیم رسید (اینبار ارجاعات به Babel، Webpack و تمام ابزارهای دیگر نیز ظاهر می‌شوند). همچنین این عملیات نیز یک طرفه‌است. یعنی از این پس قرار است کنترل تمام این پشت صحنه، در اختیار ما باشد و به روز رسانی‌های بعدی create-react-app را با مشکل مواجه می‌کند. این گزینه صرفا مختص توسعه دهندگان پیشرفته‌ی React است.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-01.zip

در قسمت بعد، پیشنیازهای جاوا اسکریپتی شروع به کار با React را بررسی می‌کنیم.
اشتراک‌ها
NuGet 6.3 منتشر شد

Announcing NuGet 6.3 - Transitive Dependencies, Floating Versions, and Re-enabling Signed Package Verification - The NuGet Blog 

NuGet 6.3 منتشر شد