نظرات مطالب
اهمیت code review
من چند وقتیه که مشترک بلاگ شما شدم. پست هاتون بسیار مفید و کاربردیه. خواستم ازتون تشکر کرده باشم.
در مورد این پست هم موافقم باهاتون. ولی من تو شرکت هایی که کار کردم معمولا یه قوانینی در این زمینه وجود داره که موقع شروع به کار برنامه نویس بهش می دن. هرچند شرکت تا شرکت فرق می کنه و گاهی به طور کامل رعایت نمیشه ولی حداقل موقعی که از اواسط یک پروژه واردش میشیم و قراره در ادامه کدهای دیگران کد بنویسیم مفیده.
البته من خودم رو برنامه نویس با تجربه ای نمی دونم اما به شخصه با وجود لایه DAL بسیار موافقم. نظم خاصی به کد میده و باعث میشه موقع تست یا بازبینی کد دیگه درگیر دیتابیس نباشیم.
یه خواهشی هم دارم میشه لطف کنید یه کم در مورد لزوم تحلیل درست پیش از شروع به کدنویسی هم بنویسید.
پایدار باشید.
مطالب
بررسی Source Generators در #C - قسمت سوم - بهبود کارآیی برنامه با تبدیل عملیات Reflection به تولید کد خودکار
همانطور که در قسمت اول این سری نیز عنوان شد، انجام عملیات Reflection عموما به همراه سربار محاسبه‌ی هرباره‌ی اطلاعات مورد نیاز آن است و اکنون می‌توان یک چنین محاسباتی را توسط Source generators، در زمان کامپایل برنامه، تامین و جزئی از خروجی نهایی کامپل شده‌ی آن کرد تا کارآیی برنامه به شدت افزایش یابد. یک نمونه مثال آن، استفاده از ویژگی Display بر روی عناصر یک enum است تا بتوان توضیحات بیشتری را جهت نمایش در UI، ارائه داد:
using System.ComponentModel.DataAnnotations;

namespace NotifyPropertyChangedGenerator.Demo;

public enum Gender
{
    NotSpecified,
    [Display(Name = "مرد")] Male,
    [Display(Name = "زن")] Female
}
روش متداول جهت دسترسی به اطلاعات ویژگی Display، استفاده از Reflection به صورت زیر است:
public static class Extensions
{
    public static string GetDisplayName(this Enum value)
    {
        if (value is null)
            throw new ArgumentNullException(nameof(value));

        var attribute = value.GetType().GetField(value.ToString())?
            .GetCustomAttributes<DisplayAttribute>(false).FirstOrDefault();

        if (attribute is null)
            return value.ToString();

        return attribute.GetType().GetProperty("Name")?.GetValue(attribute, null)?.ToString();
    }
}
یعنی هرجائی که در برنامه نیاز باشد تا برای مثال نام نمایشی Gender.Female محاسبه شود، باید یکبار عملیات فوق در زمان اجرا، تکرار گردد با محاسبه‌ی تمام ویژگی‌های یک عنصر enum، بررسی وجود DisplayAttribute در این بین و در صورت وجود، محاسبه‌ی مقدار خاصیت Name آن.
یعنی در اصل متد کمکی که برای اینکار نیاز داریم، چنین خروجی را دارد:
namespace NotifyPropertyChangedGenerator.Demo
{
  public static class GenderExtensions
  {
      public static string GetDisplayName(this Gender @enum)
      {
          return @enum switch
            {
                Gender.NotSpecified => "NotSpecified",
                Gender.Male => "مرد",
                Gender.Female => "زن",
                _ => throw new ArgumentOutOfRangeException(nameof(@enum))
            };
      }
  }
}
مزیت این روش نسبت به Reflection، از پیش محاسبه شده بودن و سرعت بالای کار با آن است؛ اما ... باید به ازای هر enum نوشته شده، یکبار به صورت اختصاصی، تکرار شود و همچنین اگر اطلاعات enum متناظر با آن تغییر کرد، نیاز است تا این کلاس‌ها و متدهای کمکی نیز اصلاح شوند. به همین جهت است که عموما کار با Reflection ترجیح داده می‌شود؛ چون حجم کدنویسی کمتری را به همراه دارد و همچنین می‌تواند انواع و اقسام enum را پوشش دهد و عمومی است.
با ارائه‌ی Source Generators، مشکلات یاد شده دیگر وجود ندارند. یعنی کار تولید متدهای اختصاصی برای هر enum، خودکار است و همچنین به روز رسانی آنی آن‌ها با هر تغییری در enum‌ها نیز پیش‌بینی شده‌است.


تهیه‌ی تولید کننده‌ی خودکار کدی که نام نمایشی enumها را به صورت از پیش محاسبه شده ارائه می‌دهد

در قسمت قبل، با روش تهیه و استفاده از Source Generators آشنا شدیم. در اینجا نیز از همان قالب، در جهت تولید کد متد الحاقی GetDisplayName فوق، استفاده خواهیم کرد. یعنی هدف رسیدن به کلاس GenderExtensions فوق و متد GetDisplayName آن، در زمان کامپایل برنامه و به صورت خودکار است:
[Generator]
public class EnumExtensionsGenerator : ISourceGenerator
{
    public void Initialize(GeneratorInitializationContext context)
    {}

    public void Execute(GeneratorExecutionContext context)
    {
        var compilation = context.Compilation;
        foreach (var syntaxTree in compilation.SyntaxTrees)
        {
            var semanticModel = compilation.GetSemanticModel(syntaxTree);
            var immutableHashSet = syntaxTree.GetRoot()
                .DescendantNodesAndSelf()
                .OfType<EnumDeclarationSyntax>()
                .Select(enumDeclarationSyntax => semanticModel.GetDeclaredSymbol(enumDeclarationSyntax))
                .OfType<ITypeSymbol>()
                /*.Where(typeSymbol => typeSymbol.GetAttributes().Any(
                    attributeData => string.Equals(attributeData.AttributeClass?.Name, "GenerateExtensions",
                        StringComparison.Ordinal)
                ))*/
                .ToImmutableHashSet();

            foreach (var typeSymbol in immutableHashSet)
            {
                var source = GenerateEnumExtensions(typeSymbol);
                context.AddSource($"{typeSymbol.Name}Extensions.cs", source);
            }
        }
    }
کار را با ایجاد یک کلاس عمومی جدید که پیاده سازی کننده‌ی اینترفیس ISourceGenerator و مزین به ویژگی Generator است، شروع می‌کنیم. در مورد وابستگی‌های مورد نیاز یک چنین پروژه‌ای، در قسمت قبل توضیحات کافی ارائه شد.
در اینجا در متد Execute، دسترسی کاملی را به اطلاعات تهیه شده‌ی توسط کامپایلر داریم. توسط آن تمام Enumهای برنامه را یا همان EnumDeclarationSyntax را در اینجا، یافته و سپس حلقه‌ای را بر روی اطلاعات آن‌ها تشکیل داده و برای تک تک آن‌ها، توسط متد GenerateEnumExtensions، کد معادل کلاس GenderExtensions را که در این مطلب معرفی شد، تولید می‌کنیم. در پایان کار نیز این کد را توسط متد AddSource، به کامپایلر معرفی خواهیم کرد تا بلافاصله در IDE ظاهر شده و قابلیت استفاده را پیدا کند.

یک نکته: اگر می‌خواهید صرفا enumهای خاصی در این بین بررسی شوند، می‌توانید کدهای یک Attribute سفارشی را مثلا با نام فرضی [GenerateExtensions] در همینجا توسط متد context.AddSource به مجموعه سورس‌ها اضافه کنید و سپس بر اساس نام آن، در قسمت Where ایی که کامنت شده‌است، تنها اطلاعات مدنظر را فیلتر و پردازش کنید.

متدی هم که ابتدا کلاس Extensions را بر اساس نام هر Enum موجود تولید و سپس بدنه‌ی متد GetDisplayName اختصاصی آن‌را تکمیل می‌کند، به صورت زیر است:
    private string GenerateEnumExtensions(ITypeSymbol typeSymbol)
    {
        return $@"namespace {typeSymbol.ContainingNamespace}
{{
  public static class {typeSymbol.Name}Extensions
  {{
      public static string GetDisplayName(this {typeSymbol.Name} @enum)
      {{
          {GenerateExtensionMethodBody(typeSymbol)}
      }}
  }}
}}";
    }

    private static string GenerateExtensionMethodBody(ITypeSymbol typeSymbol)
    {
        var sb = new StringBuilder();
        sb.Append(@"return @enum switch
            {
");

        foreach (var fieldSymbol in typeSymbol.GetMembers().OfType<IFieldSymbol>())
        {
            var displayAttribute = fieldSymbol.GetAttributes()
                .FirstOrDefault(attributeData =>
                    string.Equals(attributeData.AttributeClass?.Name, "DisplayAttribute", StringComparison.Ordinal));
            if (displayAttribute is null)
            {                
                sb.AppendLine(
                    $@"                {typeSymbol.Name}.{fieldSymbol.Name} => ""{fieldSymbol.Name}"",");
            }
            else
            {
                var displayAttributeName = displayAttribute.NamedArguments
                    .FirstOrDefault(x => string.Equals(x.Key, "Name", StringComparison.Ordinal))
                    .Value;
                sb.AppendLine(
                    $@"                {typeSymbol.Name}.{fieldSymbol.Name} => ""{displayAttributeName.Value}"",");
            }
        }

        sb.Append(
            @"                _ => throw new ArgumentOutOfRangeException(nameof(@enum))
            };");

        return sb.ToString();
    }
در مورد روش استفاده‌ی از این source generator نیز در قسمت قبل بحث شد و فقط کافی است ارجاعی را به اسمبلی آن به پروژه‌ی مدنظر افزود و OutputItemType را به آنالایزر تنظیم کرد.

کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید: SourceGeneratorTests-part3.zip


سؤال: چگونه می‌توان کدهای تولید شده‌ی توسط یک Source Generator را ذخیره کرد؟

Source Generators به صورت پیش‌فرض هیچ فایلی را بر روی دیسک سخت ذخیره نمی‌کنند و تمام عملیات آن‌ها در حافظه انجام می‌شود. اگر علاقمند به مطالعه‌ی این خروجی‌های خودکار، به صورت فایل‌های واقعی هستید، نیاز به انجام تغییرات زیر در فایل csproj پروژه‌ی مصرف کننده‌ی Source Generator است:
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>

    <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
    <CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
  </PropertyGroup>
  
  <Target Name="CleanSourceGeneratedFiles" BeforeTargets="BeforeBuild" DependsOnTargets="$(BeforeBuildDependsOn)">
    <RemoveDir Directories="$(CompilerGeneratedFilesOutputPath)" />
  </Target>  
  <ItemGroup>
    <!-- Exclude the output of source generators from the compilation -->
    <Compile Remove="$(CompilerGeneratedFilesOutputPath)/**/*.cs" />
<Content Include="$(CompilerGeneratedFilesOutputPath)/**" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\NotifyPropertyChangedGenerator\NotifyPropertyChangedGenerator.csproj" 
OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
  </ItemGroup>
</Project>
توضیحات:
- EmitCompilerGeneratedFiles سبب ثبت فایل‌های خودکار تولید شده، بر روی دیسک سخت می‌شود که قالب مسیر پیش‌فرض ذخیره سازی آن به صورت زیر است:
{BaseIntermediateOutpath}/generated/{Assembly}/{SourceGeneratorName}/{GeneratedFile}
- اگر می‌خواهید نام پوشه‌ی generated را تغییر دهید، می‌توان از ویژگی CompilerGeneratedFilesOutputPath استفاده کرد.
- چون این فایل‌های cs. جدید ثبت شده‌ی بر روی دیسک سخت، مجددا وارد پروسه‌ی کامپایل می‌شوند و خود Source Generator هم یک نمونه‌ی از آن‌ها‌را پیش‌تر به کامپایلر معرفی کرده‌است، برنامه دیگر به علت وجود اطلاعات تکراری، کامپایل نخواهد شد. به همین جهت نیاز است تا قسمت Compile Remove فوق را نیز معرفی کرد تا کامپایلر از پوشه‌ی Generated تنظیمی، صرفنظر کند.
- اطلاعات موجود در پوشه‌ی Generated، فقط یکبار تولید می‌شوند؛ صرفنظر از اطلاعات موجود در حافظه که همیشه به روز است. به همین جهت اگر می‌خواهید نمونه‌های به روز شده‌ی آن‌ها را نیز بر روی دیسک سخت داشته باشید، نیاز به قسمت RemoveDir تنظیمی وجود دارد.
نظرات مطالب
یکی از مزایای استفاده از SVN در یک پروژه تک نفره
سلام
با استفاده از دستور زیر خروجی xml تمام commit های صورت گرفته به همراه کامنت‌ها و زمان و غیره را می‌شود بدست آورد:
svn log --xml file:///C:/Repositories/project1/trunk > logs.xml

همچنین در فصل آخر جزوه‌ای که نامبرده شد روش دسترسی به svn با برنامه نویسی هم ذکر شده که هر نوع خروجی که مایل بودید به این صورت می‌شود تهیه کرد.
مطالب
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 را بررسی می‌کنیم.
مطالب
پشتیبانی از کشیدن و رها کردن در Angular 7
یکی از قابلیت‌هایی که به Angular 7 اضافه شده‌است، پشتیبانی از Drag and Drop می‌باشد. برای استفاده از این قابلیت، در ابتدا لازم است Angular CLI را بروز رسانی کنیم و بعد از آن پکیج @angular/cdk  را نصب و ماژول DragDropModule را در فایل app.module.ts، بخش import  اضافه کنیم:
npm install @angular/cdk

app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { DragDropModule } from '@angular/cdk/drag-drop';
import { FormsModule } from '@angular/forms';

@NgModule({
  declarations: [ AppComponent ],
  imports: [
    BrowserModule,
    FormsModule,
    DragDropModule
  ],
  bootstrap: [ AppComponent ]
})
export class AppModule {}

cdkDrag
با استفاده از دایرکتیو cdkDrag، یک آیتم قابلیت جابجایی را پیدا می‌کند و به صورت پیش فرض این دایرکتیو اجازه جابجایی آیتم را در تمامی جهات می‌دهد:
<div cdkDrag>
  I'm Draggable
</div>

در صورتیکه قصد داشته باشید جابجایی را بر اساس محور x یا y، محدود کنید، از دایرکتیو cdkDragLockAxis استفاده می‌شود که می‌تواند مقدار x یا y را نیز بپذیرد:
<div cdkDrag cdkDragLockAxis="x">
  I'm Draggable
</div>

در ادامه قصد پیاده سازی مثال زیر را داریم :

در ابتدا یک مدل را ایجاد می‌کنیم: 

export interface Todo {
    title: string;
    type?: string;
}


فایل app.component.ts 

export class AppComponent implements OnInit {

  public title = 'Darg and drop';
  public model: Todo;

  public todo: Todo[];
  public done: Todo[];
  public cancelled: Todo[];


  ngOnInit(): void {
    this.setDefalutValue();
  }

  addItem(form, $event: Event) {
    $event.preventDefault();
    if (form.valid) {
      if (this.model.type === 'todo') {
        this.todo.push({ title: this.model.title });
      } else {
        this.done.push({ title: this.model.title });
      }
    } else {
      alert('فرم معتبر نمی‌باشد . عنوان را وارد نمایید');
    }
  }

  drop(event: CdkDragDrop<Todo[]>) {
    if (event.previousContainer === event.container) {
      moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
    } else {
      transferArrayItem(event.previousContainer.data, event.container.data, event.previousIndex, event.currentIndex);
    }
  }



  private setDefalutValue() {
    this.todo = [
      { title: 'خرید مواد غذایی' },
      { title: 'رفتن به خانه' },
      { title: 'خوابیدن' }
    ];

    this.done = [
      { title: 'بیدار شدن' },
      { title: 'مسواک زدن' },
      { title: 'دوش گرفتن' },
      { title: 'چک کردن ایمیل' }
    ];

    this.cancelled = [];

    this.model = {
      title: null,
      type: 'todo'
    };

  }

}


در اینجا 3 آرایه یکی برای to-do  و یکی برای Done  و دیگری برای Cancelled  ایجاد می‌کنیم و به هر کدام تعدادی آیتم را اضافه می‌کنیم.

addItem : زمانیکه فرم را submit می‌کنیم اجرا می‌شود . 


drop : زمانی اجرا می‌شود که یک آیتم را Drag and Drop کنیم. در ایجا شرط event.previousContainer === event.container  زمانی درست است که جابجایی در درون یک لیست باشد و هدف در این صورت، مرتب سازی است .

moveItemInArray : ایندکس آیتم‌ها را در همان لیست تغیر می‌دهد (مرتب سازی).

transferArrayItem: آیتم را از یک لیست حذف و به لیست دیگری اضافه می‌کند.


فایل app.component.html

  <div>
    <!-- فرم -->
    <div>
      <fieldset>
        <legend>
          اضافه کردن آیتم جدید
        </legend>
        <form #form="ngForm" (submit)="addItem(form,$event)">

          <label></label>
          <input type="text" required name="title" #name="ngModel" [(ngModel)]="this.model.title">
          <label></label>

          <select required name="type" #type="ngModel" [(ngModel)]="this.model.type">
            <option value="todo">
              انجام دادن
            </option>
            <option value="done">
              انجام شده
            </option>
          </select>

          <input type="submit" value="ذخیره">
        </form>
      </fieldset>
    </div>

    <!-- آیتم‌ها -->
    <div>
      <fieldset>
        <legend>
          لیست آیتم‌ها </legend>

        <div>
          <!-- انجام دادن -->
          <div>
            <p>
              انجام دادن
            </p>
            <div cdkDropList #todoList="cdkDropList" [cdkDropListData]="todo" [cdkDropListConnectedTo]="[doneList, cancelledList]"
              (cdkDropListDropped)="drop($event)">
              <div *ngFor="let item of todo" cdkDrag>
                <p> {{ item.title | titlecase }} </p>
              </div>
            </div>
          </div>

          <!-- انجام شده -->
          <div>
            <p>
              انجام شده
            </p>

            <div cdkDropList #doneList="cdkDropList" [cdkDropListData]="done" [cdkDropListConnectedTo]="[todoList, cancelledList]"
              (cdkDropListDropped)="drop($event)">
              <div *ngFor="let item of done" cdkDrag>
                <p> {{ item.title | titlecase }} </p>
              </div>
            </div>

          </div>

          <!-- انجام نشده -->
          <div>
            <p>
              انجام نشده
            </p>
            <div cdkDropList #cancelledList="cdkDropList" [cdkDropListData]="cancelled" [cdkDropListConnectedTo]="[todoList, doneList]"
              (cdkDropListDropped)="drop($event)">
              <div *ngFor="let item of cancelled" cdkDrag>
                <p> {{ item.title | titlecase }} </p>
              </div>
            </div>
          </div>

        </div>

      </fieldset>
    </div>
  </div>


در ابتدا یک فرم داریم که در اینجا همه چیز مشخص است ( فرم‌های مبتنی بر قالب‌ها در Angular )

در ادامه 3 container را ایجاد می‌کنیم یکی برای to-do  و یکی برای Done  و در آخر یکی برای Cancelled

container  ایجاد شده برای  to-do

<div cdkDropList #todoList="cdkDropList" [cdkDropListData]="todo" [cdkDropListConnectedTo]="[doneList, cancelledList]"
     (cdkDropListDropped)="drop($event)">
   <div *ngFor="let item of todo" cdkDrag>
      <p> {{ item.title | titlecase }} </p>
   </div>
</div>


توضیحات: 

cdkDropList یک container می‌باشد، برای آیتم‌هایی که قرار است Drag and Drop شوند. 

todoList #:

    id مربوط به container را مشخص می‌کند.

cdkDropListConnectedTo:

 id مربوط به container های دیگری که می‌تواند آیتم های container جاری را بپذیرد.

cdkDropListData مشخص کنند منبع داده است.

cdkDropListDropped:  این رویداد زمانی اجرا می‌شود که Drag and Drop برای یک آیتم انجام شود.

cdkDrag:  برای اینکه آیتم‌های درون یک container قابلیت Drag and Drop را داشته باشند، این دایرکتیو را  اضافه می‌کنیم.


تمام ! 


DEMO 

اشتراک‌ها
کتابخانه نمایش فرمول های ریاضی در React

این کتابخانه که توسط آقای مهدی صادقی ایجاد شده است قابلیت نمایش فرمول‌های ریاضی را به صورت یک کامپوننت ساده و عدم درگیری مستقیم با کتابخانه MathJax و life cycle‌ها ایجاد کرده است:

  <MathJax sanitizeOptions={ {USE_PROFILES: {html: true,mathMl: true}}} style={{'display':'inline-block'}} math={this.props.question.title} />

همچنین به دلیل بکارگیری کتابخانه DOMPurify  امکان مدیریت تگ‌ها و جلوگیری از حملات xss و .. نیز فراهم میباشد.

کتابخانه نمایش فرمول های ریاضی در React
نظرات مطالب
مقابله با XSS ؛ یکبار برای همیشه!
معادل HtmlCleaner ایی که در این مطلب مطرح شده برای NET Core.

ابتدا بسته‌ی نیوگت DNTCommon.Web.Core را نصب کنید:
PM> Install-Package DNTCommon.Web.Core
سپس به فایل appsettings.json، تنظیمات تگ‌ها و ویژگی‌های مجاز را اضافه کنید. این تنظیمات باید در فایل آغازین برنامه خوانده شوند.
پس از آن یک نمونه مثال از IAntiXssService آن‌را جهت تمیزسازی قطعه HTML دریافتی، در اینجا می‌توانید مشاهده کنید.