مقدمه:
مدیریت آزمون مایکروسافت یا Microsoft Test Manager یک ابزار تست نویسی است که به تسترها این اجازه را میدهد تا بتوانند برای UI برنامههای خود یا sprintهای پروژه خود تست بنویسند. این ابزار برای نوشتن آزمونهای پیشرفته و مجتمع سازی مدیریت طرحهای تست یا test plans همراه با موردهای تست یا test case در طول توسعه برنامه است. یکی از مزایایی که این ابزار دارد این است که در طول انجام تست میتوانید اشکالات تست را ثبت کنید و هم چنین میتوانید شرحی در مورد انجام تست یا اشکالی که در آن تست وجود دارد، ثبت کنید. همچنین میتوانید گزارشی از تست هایی که انجام داده اید و پاس شدن یا پاس نشدن تستها و تاریخ انجام آنها را نیز مشاهده کنید. قبل از کار با نرم افزار MTM باید یک سری مطالب مهم را در مورد انجام تست و مفهوم Agile بدانیم.
استراتژی تست:
زمانی که شما تست Agile را معرفی میکنید تیم برنامه نویسی شما میتواند بر روی تستهای شما هم در سطح sprint و هم در سطح پروژه تمرکز کنند. تست در سطح sprint شامل تست هایی میشود که همه user storyها در بر بگیرد یعنی در واقع همان تستهای واحد شما میشود. در سطح پروژه هم شامل تست هایی میشود که چندین sprint را در بر میگیرد که در واقع میتوان تستهای integrated گفت. بهتر است زمانی که تیم برنامه نویسی کدنویسی میکنند شما طرح تستهای خود را بسازید و برای انجام تست کاملا آماده باشید. این تستها شامل تست واحد، تست performance، تست امنیتی و تست usability و غیره میباشد.
برای آماده کردن تست Agile در ابتدا شما باید یک تاریخچه یا history از برنامه یا سیستم خود داشته باشید. شما میتوانید با استفاده از Microsoft Test Manager طرح تست خود را برای هر یک از sprintهای پروژتان بسازید و موردهای تست را مشخص کنید.
سپس باید کدهایی که برنامه نویسان مینویسند قابلیت تست را داشته باشند و شما به عنوان یک تستر باید آشنایی کاملی از ساختار و الگوهای برنامه تان داشته باشید.
تست یک فرآیند تکراری میباشد که همزمان با اجرای پروژه تان صورت میگیرد در زیر میتوانید فرآیند کار تست و انجام کدنویسی را مشاهده نمایید:
Test Planning:
Test Planning فرآیندی است که به تیم شما کمک میکند تا درک درستی از پروژه داشته باشند و همچنین تیم را برای انجام هر گونه تستی آماده کند. تست Agile در سطح Sprint انجام میشود که در هر Sprint تیم شما تست هایی را ایجاد میکنند تا user story هایی که در هر Sprint وجود دارد، مورد بررسی قرار گیرند. در شکل زیر قالبی از test planهای شما در یک پروژه را نمایش میدهد:
البته این قالبها بر اساس سلیقه شخصی است اما در کل میتوانیم قالب تست را به صورت بالا در نظر بگیریم.
همیشه باید این را در نظر داشته باشیم که در طول هر sprint حتما باید تستها را اجرا کرده و در صورت وجود خطا، آن خطا را رفع کنیم تا در مراحل بالاتر با مشکلی مواجه نشویم. در قسمت بعد با Microsoft Test Manager و روشهای نوشتن sprint و تستها آشنا خواهیم شد.
درخواست
MVC Scaffolding #1
کل سری ASP.NET MVC
به همراه کل سری EF Code First
MVC Scaffolding چیست؟
MVC Scaffolding ابزاری است برای تولید خودکار کدهای «اولیه» برنامه، جهت بالا بردن سرعت تولید برنامههای ASP.NET MVC مبتنی بر EF Code First.
بررسی مقدماتی MVC Scaffolding
امکان اجرای ابزار MVC Scaffolding از دو طریق دستورات خط فرمان Powershell و یا صفحه دیالوگ افزودن یک کنترلر در پروژههای ASP.NET MVC وجود دارد. در ابتدا حالت ساده و ابتدایی استفاده از صفحه دیالوگ افزودن یک کنترلر را بررسی خواهیم کرد تا با کلیات این فرآیند آشنا شویم. سپس در ادامه به خط فرمان Powershell که اصل توانمندیها و قابلیتهای سفارشی MVC Scaffolding در آن قرار دارد، خواهیم پرداخت.
برای این منظور یک پروژه جدید MVC را آغاز کنید؛ ابزارهای مقدماتی MVC Scaffolding از اولین به روز رسانی ASP.NET MVC3 به بعد با VS.NET یکپارچه هستند.
ابتدا کلاس زیر را به پوشه مدلهای برنامه اضافه کنید:
using System; using System.ComponentModel; using System.ComponentModel.DataAnnotations; namespace MvcApplication1.Models { public class Task { public int Id { set; get; } [Required] public string Name { set; get; } [DisplayName("Due Date")] public DateTime? DueDate { set; get; } [DisplayName("Is Complete")] public bool IsComplete { set; get; } [StringLength(450)] public string Description { set; get; } } }
همانطور که ملاحظه میکنید در قسمت قالبها، تولید کنترلرهایی با اکشن متدهای ثبت و نمایش اطلاعات مبتنی بر EF Code First انتخاب شده است. کلاس مدل نیز به کلاس Task فوق تنظیم گردیده و در زمان انتخاب DbContext مرتبط، گزینه new data context را انتخاب کرده و نام پیش فرض آنرا پذیرفتهایم. زمانیکه بر روی دکمه Add کلیک کنیم، اتفاقات ذیل رخ خواهند داد:
الف) کنترلر جدید TasksController.cs به همراه تمام کدهای Insert/Update/Delete/Display مرتبط تولید خواهد شد.
ب) کلاس DbContext خودکاری به نام MvcApplication1Context.cs در پوشه مدلهای برنامه ایجاد میگردد تا کلاس Task را در معرض دید EF Code first قرار دهد. (همانطور که عنوان شد یکی از پیشنیازهای بحث Scaffolding آشنایی با EF Code first است)
ج) در پوشه Views\Tasks، پنج View جدید را جهت مدیریت فرآیندهای نمایش صفحات Insert، حذف، ویرایش، نمایش و غیره تهیه میکند.
د) فایل وب کانفیگ برنامه جهت درج رشته اتصالی به بانک اطلاعاتی تغییر کرده است. حالت پیش فرض آن استفاده از SQL CE است و برای استفاده از آن نیاز است قسمت 15 سری EF سایت جاری را پیشتر مطالعه کرده باشید (به چه اسمبلیهای دیگری مانند System.Data.SqlServerCe.dll برای اجرا نیاز است و چطور باید اتصال به بانک اطلاعاتی را تنظیم کرد)
معایب:
کیفیت کد تولیدی پیش فرض قابل قبول نیست:
- DbContext در سطح یک کنترلر وهله سازی شده و الگوی Context Per Request در اینجا بکارگرفته نشده است. واقعیت یک برنامه ASP.NET MVC کامل، داشتن چندین Partial View تغدیه شونده از کنترلرهای مختلف در یک صفحه واحد است. اگر قرار باشد به ازای هر کدام یکبار DbContext وهله سازی شود یعنی به ازای هر صفحه چندین بار اتصال به بانک اطلاعاتی باید برقرار شود که سربار زیادی را به همراه دارد. (قسمت 12 سری EF سایت جاری)
- اکشن متدها حاوی منطق پیاده سازی اعمال CRUD یا همان Create/Update/Delete هستند. به عبارتی از یک لایه سرویس برای خلوت کردن اکشن متدها استفاده نشده است.
- از ViewModel تعریف شدهای به نام Task هم به عنوان Domain model و هم ViewModel استفاده شده است. یک کلاس متناظر با جداول بانک اطلاعاتی میتواند شامل فیلدهای بیشتری باشد و نباید آنرا مستقیما در معرض دید یک View قرار داد (خصوصا از لحاظ مسایل امنیتی).
مزیتها:
قسمت عمدهای از کارهای «اولیه» تهیه یک کنترلر و همچنین Viewهای مرتبط به صورت خودکار انجام شدهاند. کارهای اولیهای که با هر روش و الگوی شناخته شدهای قصد پیاده سازی آنها را داشته باشید، وقت زیادی را به خود اختصاص داده و نهایتا آنچنان تفاوت عمدهای هم با کدهای تولیدی در اینجا نخواهند داشت. حداکثر فرمهای آنرا بخواهید با jQuery Ajax پیاده سازی کنید یا کنترلهای پیش فرض را با افزونههای jQuery غنی سازی نمائید. اما شروع کار و کدهای اولیه چیزی بیشتر از این نیست.
نصب بسته اصلی MVC Scaffolding توسط NuGet
بسته اصلی MVC Scaffolding را با استفاده از دستور خط فرمان Powershell ذیل، از طریق منوی Tools، گزینه Library package manager و انتخاب Package manager console میتوان به پروژه خود اضافه کرد:
Install-Package MvcScaffolding
Attempting to resolve dependency 'T4Scaffolding'. Attempting to resolve dependency 'T4Scaffolding.Core'. Attempting to resolve dependency 'EntityFramework'. Successfully installed 'T4Scaffolding.Core 1.0.0'. Successfully installed 'T4Scaffolding 1.0.8'. Successfully installed 'MvcScaffolding 1.0.9'. Successfully added 'T4Scaffolding.Core 1.0.0' to MvcApplication1. Successfully added 'T4Scaffolding 1.0.8' to MvcApplication1. Successfully added 'MvcScaffolding 1.0.9' to MvcApplication1.
پس از اینکه بسته MvcScaffolding به پروژه جاری اضافه شد، همان مراحل قبل را که توسط صفحه دیالوگ افزودن یک کنترلر انجام دادیم، اینبار به کمک دستور ذیل نیز میتوان پیاده سازی کرد:
Scaffold Controller Task
نکته و مزیت مهم دیگری که در اینجا در دسترس میباشد، سوئیچهای خط فرمانی است که به همراه صفحه دیالوگ افزودن یک کنترلر وجود ندارند. برای مثال دستور Scaffold Controller را تایپ کرده و سپس یک خط تیره را اضافه کنید. اکنون دکمه tab را مجددا بفشارید. منویی ظاهر خواهد شد که بیانگر سوئیچهای قابل استفاده است.
برای مثال اگر بخواهیم دستور Scaffold Controller Task را با جزئیات اولیه کاملتری ذکر کنیم، مانند تعیین نام دقیق کلاس مدل و کنترلر تولیدی به همراه نام دیگری برای DbContext مرتبط، خواهیم داشت:
Scaffold Controller -ModelType Task -ControllerName TasksController -DbContextType TasksDbContext
بهبود مقدماتی کیفیت کد تولیدی MVC Scaffolding
در همان کنسول پاروشل NuGet، کلید up arrow را فشار دهید تا مجددا دستور قبلی اجرا شده ظاهر شود. اینبار دستور قبلی را با سوئیچ جدید Repository (استفاده از الگوی مخزن) اجرا کنید:
Scaffold Controller -ModelType Task -ControllerName TasksController -DbContextType TasksDbContext -Repository
Scaffold Controller -ModelType Task -ControllerName TasksController -DbContextType TasksDbContext -Repository -Force
public class TasksController : Controller { private readonly ITaskRepository taskRepository; // If you are using Dependency Injection, you can delete the following constructor public TasksController() : this(new TaskRepository()) { } public TasksController(ITaskRepository taskRepository) { this.taskRepository = taskRepository; }
public interface ITaskRepository : IDisposable { IQueryable<Task> All { get; } IQueryable<Task> AllIncluding(params Expression<Func<Task, object>>[] includeProperties); Task Find(int id); void InsertOrUpdate(Task task); void Delete(int id); void Save(); }
پیاده سازی این اینترفیس در حالت متد Save آن شامل فراخوانی context.SaveChanges است. این مورد باید به الگوی واحد کار (که در اینجا تعریف نشده) منتقل شود. زیرا در یک دنیای واقعی حاصل کار بر روی چندین موجودیت باید در یک تراکنش ذخیره شوند و قرارگیری متد Save داخل کلاس مخزن یا سرویس برنامه، مخزنهای تعریف شده را تک موجودیتی میکند.
اما در کل با توجه به اینکه پیاده سازی منطق کار با موجودیتها به کلاسهای مخزن واگذار شدهاند و کنترلرها به این نحو خلوتتر گردیدهاند، یک مرحله پیشرفت محسوب میشود.
اول اینکه، کیفیت دادههای ذخیرهشده در دستگاههای تلفن همراه کاربر بیشتر شخصی میشود تا مواردی دیگر! به غیر از ایمیل، پیامهای فوری، SMS / MMS ، لاگ تماسها، عکسها و پست صوتی وجود دارند که عموما توسعه دهندگان را دچار مشکل میکند.
برخی از گزینههای فوق بر روی یک کامپیوتر رومیزی هم وجود دارند، ولی اهمیت این دادهها بر روی اندروید و اجزای آن اهمیت فوق العادهای دارد. اطلاعات روی دستگاه موبایل شما به احتمال زیاد از ارزش بیشتری برخوردار خواهد بود، چرا که آنها را در یک صفحه 4 - 5 اینچی به همراه خود حمل میکنید و با خود هر کجا میبرید! این حالت، یک پلتفرم همگرا را بوجود میآورد؛ به این دلیل که سیستم رومیزی شما و تلفن همراه یک مجموعه غنی و کامل از اطلاعات حساس هستند که هردوی آنها شامل اطلاعات شخصی میباشند و برای شما اهمیت زیادی خواهند داشت. تصور کنید زمانیکه برای جلوگیری از نفوذ یا به سرقت رفتن شماره تلفنهای خود، یک پشتیبان بر روی سیستم رو میزی خود تهیه میکنید و فایل پشتیبان شمارههای تماس را بر روی سیستم شخصی نگه داری میکنید! آیا این همان پلتفرم همگرا نیست؟ آیا این دو سیستم مکمل هم نیستند؟حتی اگر همگامسازی را با یک مکان دوردست (Google Drive) انجام دهید، با این حال شما فقط در مقابل از دست دادن دادهها محافظت کردهاید و نه از دست دادن حریم خصوصی!
همچنین در نظر بگیرید که فرمت دادههای ذخیرهشده در دستگاههای تلفن همراه، تعیین و مشخص شوند! این کار اطلاعات حساس شما را به مرز سرقت نزدیکتر میکند. هر تلفن همراه SMS / MMS ، تماسها، و پست صوتی خواهد داشت. مکانهای ذخیره شده از روی GPS و مواردی دیگر که قطعا اطلاع دارید، تمامی اینها جزء مواردی هستند که خطرات امنیتی را در سیستم عامل اندروید شامل میشود. حالا در نظر بگیرید که این اطلاعات تا چه حد مهم است؟ برای کاربرانی که هیچ گونه پشتیبانی از اطلاعاتی از خود ندارند، از دست دادن دادهها قابل تصور نیست!
خطرناکترین نوع حملات بر روی پلتفرم اندروید انجام میشوند، در سکوت کامل و چندین هزار مایل دروتر از شما و فرد مهاجم نیازی به دسترسی فیزیکی و لمس تلفن همراه شما نخواهد داشت! این نوع حملات در هر زمانی ممکن است رخ دهد و اغلب میتواند به دلیل امنیت ضعیف در جای دیگری بر روی دستگاه رخ دهد.
در مطلب بعدی پیرامون امنیت معماری اندروید صبحت خواهیم کرد...
وبلاگها ، سایتها و مقالات ایرانی (داخل و خارج از ایران)
- علت مرگ نرم افزار ها
- مدیریت ساده تر Group policy – قسمت دوم
- آموزشCCNA لایه های OSI(بخش4)
- تاریخ بردار میلادی و شمسی (جلالی) !
- وجدان برنامه نویسی و اجبار برنامه نویسی
- دسترسی به کنترل های Server side از طریق جاوا اسکریپت
- MikroTik EOIP Tunnel
- انتشار SMF 1.1.8
- دسترس پذیری وب: مروری بر یک پژوهش؛ بخش اول
- سوییچ های Catalyst
- Tab Order، به راحتی آب خوردن !
- 20+ افزونه فایرفاکس برای توسعه دهندگان و طراحان وب
امنیت
Visual Studio
ASP. Net
طراحی و توسعه وب
- بهبودهای حاصل شده در کارآیی مرورگرها
- بیش از 40 مقاله آموزشی jQuery
- مجموعهای از کتابخانههای آپلود فایل
اسکیوال سرور
سی شارپ
عمومی دات نت
مسایل اجتماعی و انسانی برنامه نویسی
- اگر معمارها روزی مانند برنامه نویسها قرار بود کار کنند ...
- وضع اعضای تیم شما چطور است؟
- 14 ابزار مدیریت پروژه
کتابهای رایگان جدید
متفرقه
- آموزش رایگان جاوا
- برگه تقلب SVN
- مقایسه سرعت یک سری از پلتفرمهای برنامه نویسی و یک نمونه دیگر
- Valentine’s day timestamp
- چگونه بهتر بنویسیم؟
- MCSE از نوع ایرانی!
نحوهی رندر لیستی از اشیاء در کامپوننتهای React
فرض کنید میخواهیم لیستی از تگها را رندر کنیم. برای این منظور ابتدا دادههای مرتبط را به خاصیت state کامپوننت، اضافه میکنیم:
class Counter extends Component { state = { count: 0, tags: ["tag 1", "tag 2", "tag 3"] };
در مطلب «React 16x - قسمت 3 - بررسی پیشنیازهای جاوا اسکریپتی - بخش 2» در مورد متد Array.map بحث شد. در اینجا میتوان توسط متد map، هر المان آرایهی تگها را به یک المان React تبدیل و سپس رندر کرد:
class Counter extends Component { state = { count: 0, tags: ["tag 1", "tag 2", "tag 3"] }; render() { return ( <div> <span className={this.getBadgeClasses()}>{this.formatCount()}</span> <button className="btn btn-secondary btn-sm">Increment</button> <ul> {this.state.tags.map(tag => ( <li>{tag}</li> ))} </ul> </div> ); }
هرچند اکنون لیستی از تگها در مرورگر رندر شدهاند، اما در کنسول توسعه دهندگان مرورگر، یک اخطار نیز درج شدهاست. علت اینجا است که React نیاز دارد تا بتواند هر آیتم رندر شده را به صورت منحصربفردی شناسایی کند. هدف این است که بتواند در صورت تغییر state هر المان در DOM مجازی خودش، خیلی سریع تشخیص دهد که چه چیزی تغییر کرده و فقط کدام قسمت خاص را باید در DOM اصلی، درج و به روز رسانی کند. برای رفع این مشکل، ویژگی key را به هر المان li در کدهای فوق اضافه میکنیم:
<li key={tag}>{tag}</li>
رندر شرطی عناصر در کامپوننتهای React
در اینجا میخواهیم اگر تگی وجود نداشت، پیام متناسبی ارائه شود؛ در غیراینصورت لیست تگها همانند قبل نمایش داده شود (رندر شرطی یا conditional rendering). برای انجام اینکار در React، برخلاف Angular، دارای دایرکتیوهای ساختاری if/else نیستیم؛ چون همانطور که عنوان شد، JSX یک templating engine نیست. به همین جهت برای رندر شرطی المانها در React، باید از همان جاوا اسکریپت خالص کمک بگیریم:
renderTags() { if (this.state.tags.length === 0) { return <p>There are no tags!</p>; } return ( <ul> {this.state.tags.map(tag => ( <li key={tag}>{tag}</li> ))} </ul> ); }
render() { return ( <div> <span className={this.getBadgeClasses()}>{this.formatCount()}</span> <button className="btn btn-secondary btn-sm">Increment</button> {this.renderTags()} </div> ); }
state = { count: 0, tags: [] };
روش دوم حل این نوع مسالهها، استفاده از روش زیر است؛ در این حالت خاص، فقط یک if را داریم، بدون وجود قسمت else:
{this.state.tags.length === 0 && "Please create a new tag!"}
اما این روش چگونه کار میکند؟! در اینجا && را به دو مقدار مشخص اعمال کردهایم. یکی حاصل یک مقایسه است و دیگری یک مقدار رشتهای مشخص. در جاوا اسکریپت برخلاف سایر زبانهای برنامه نویسی، میتوان && را بین دو مقدار غیر Boolean نیز اعمال کرد. در جاوا اسکریپت، یک رشتهی خالی به false تعبیر میشود و اگر تنها دارای یک حرف باشد، true درنظر گرفته میشود. برای نمونه در ترکیب 'true && 'Hi، هر دو قسمت به true تفسیر میشوند. در این حالت موتور جاوا اسکریپت، دومین عبارت (آخرین عبارت && شده) را بازگشت میدهد. همچنین در جاوا اسکریپت عدد صفر به false تفسیر میشود. بنابراین ترکیب true && 'Hi' && 1 مقدار 1 را بازگشت میدهد؛ چون عدد 1 هم از دیدگاه جاوا اسکریپت به true تفسیر خواهد شد.
مدیریت رخدادها در React
همانطور که در تصویر فوق نیز مشاهده میکنید، رخدادهای استاندارد DOM، دارای خواص معادل React ای نیز هستند. برای مثال زمانیکه مینویسیم onClick، دقیقا متناظر است با یک خاصیت المان React در عبارات JSX. بنابراین این نامها حساس به کوچکی و بزرگی حروف نیز هستند.
روش تعریف متدهای رخدادگردان در اینجا، با ذکر فعل handle شروع میشود:
handleIncrement() { console.log("Increment clicked!"); }
<button onClick={this.handleIncrement} className="btn btn-secondary btn-sm" > Increment </button>
اکنون اگر این فایل را ذخیره کرده و خروجی را در مرورگر بررسی کنیم، با هربار کلیک بر روی دکمهی Increment، یک console.log صورت میگیرد.
در ادامه میخواهیم در این رخدادگردان، مقدار this.state.count را افزایش دهیم. برای این منظور ابتدا مقدار this.state.count را به نحو زیر لاگ میکنیم:
handleIncrement() { console.log("Increment clicked!", this.state.count); }
bind مجدد شیء this در رخدادگردانهای React
در مورد this و bind مجدد آن در مطلب «React 16x - قسمت 2 - بررسی پیشنیازهای جاوا اسکریپتی - بخش 1» مفصل بحث کردیم و در اینجا میخواهیم از نتایج آن استفاده کنیم.
همانطور که مشاهده کردید، در متد رویدادگران handleIncrement، به شیء this دسترسی نداریم. چرا؟ چون this در جاوا اسکریپت نسبت به سایر زبانهای برنامه نویسی، متفاوت رفتار میکند. بسته به اینکه یک متد یا تابع، چگونه فراخوانی میشود، this میتواند اشیاء متفاوتی را بازگشت دهد. اگر تابعی به عنوان یک متد و جزئی از یک شیء فراخوانی شود، this در این حالت همواره ارجاعی را به آن شیء باز میگرداند. اما اگر آن تابع به صورت متکی به خود فراخوانی شد، به صورت پیشفرض ارجاعی را به شیء سراسری window مرورگر، بازگشت میدهد و اگر strict mode فعال باشد، تنها undefined را بازگشت میدهد. به همین جهت است که در اینجا خطای undefined بودن this را دریافت میکنیم.
یک روش حل این مشکل که پیشتر نیز در مورد آن توضیح دادیم، استفاده از متد bind است:
constructor() { super(); console.log("constructor", this); this.handleIncrement = this.handleIncrement.bind(this); }
اکنون اگر برنامه را اجرا کنید، با کلیک بر روی دکمهی Increment، بجای this.state.count لاگ شده، مقدار آن که صفر است، در کنسول توسعه دهندههای مرورگر ظاهر میشود.
این یک روش است که کار میکند؛ اما کمی طولانی است و به ازای هر روال رویدادگردانی باید دقیقا به همین نحو تکرار شود. روش دیگر، تبدیل متد handleIncrement به یک arrow function است و همانطور که در قسمت دوم این سری نیز بررسی کردیم، arrow functionها، this شیء جاری را بازنویسی نمیکنند؛ بلکه آنرا به ارث میبرند. بنابراین ابتدا کدهای سازندهی فوق را حذف میکنیم (چون دیگر نیازی به آنها نیست) و سپس متد handleIncrement سابق را به صورت زیر، تبدیل به یک arrow function میکنیم:
handleIncrement = () => { console.log("Increment clicked!", this.state.count); }
به روز رسانی state در کامپوننتهای React
اکنون که در روال رویدادگردان handleIncrement به شیء this و سپس مقدار this.state.count آن دسترسی پیدا کردهایم، میخواهیم با هربار کلیک بر روی این دکمه، یک واحد مقدار آنرا افزایش داده و در UI نمایش دهیم.
در React، خواص شیء state را جهت نمایش آنها در UI، مستقیما تغییر نمیدهیم. به عبارت دیگر نوشتن یک چنین کدی در React برای به روز رسانی UI، مرسوم نیست:
handleIncrement = () => { this.state.count++; };
در کدهای فوق هرچند با کلیک بر روی دکمهی Increment، مقدار count افزایش یافتهاست، اما React از وقوع این تغییرات مطلع نیست. به همین جهت است که هیچ تغییری را در UI برنامه مشاهده نمیکنید.
با اجرای قطعه کد فوق، یک چنین اخطاری نیز در کنسول توسعه دهندگان مرورگر ظاهر میشود:
Line 33:5: Do not mutate state directly. Use setState() react/no-direct-mutation-state
برای رفع این مشکل باید از یکی از متدهای به ارث برده شدهی از کلاس پایهی Component، به نام setState استفاده کرد. به این ترتیب به React اعلام میکنیم که state تغییر کردهاست (فعالسازی Change Detection، فقط در صورت نیاز). سپس React شروع به محاسبهی تغییرات کرده و در نتیجه قسمتهای متناظری از UI را برای هماهنگ سازی DOM مجازی خودش با DOM اصلی، به روز رسانی میکند.
زمانیکه از متد setState استفاده میکنیم، شیءای را باید به صورت یک پارامتر به آن ارسال کنیم. در این حالت مقادیر آن یا به خاصیت state جاری اضافه میشوند و یا در صورت از پیش موجود بودن، همان خواص را بازنویسی میکنند:
handleIncrement = () => { this.setState({ count: this.state.count + 1 }); };
در این مرحله، فایل جاری را ذخیره کرده و پس از بارگذاری مجدد برنامه در مرورگر، بر روی دکمهی Increment کلیک کنید. اینبار ... کار میکند! چون React از تغییرات مطلع شدهاست:
وقتی state تغییر میکند، چه اتفاقاتی رخ میدهند؟
با فراخوانی متد this.setState، به React اعلام میکنیم که state یک کامپوننت قرار است تغییر کند. سپس React فراخوانی مجدد متد Render را در صف اجرایی خودش قرار میدهد تا در زمانی در آینده، اجرا شود؛ این فراخوانی async است. کار متد render، بازگشت یک المان جدید React است. در اینجا DOM مجازی React از چند المان، به صورت یک div و دو فرزند دکمه و span تشکیل شدهاست. در این حالت یک DOM مجازی قدیمی نیز از قبل (پیش از اجرای مجدد متد render) وجود دارد. در این لحظه، React این دو DOM مجازی را کنار هم قرار میدهد و محاسبه میکند که در اینجا دقیقا کدام المانها نسبت به قبل تغییر کردهاند. برای نمونه در اینجا تشخیص میدهد که span است که تغییر کرده، چون مقدار count، توسط آن نمایش داده میشود. در این حالت از کل DOM اصلی، تنها همان span تغییر کرده را به روز رسانی میکند و نه کل DOM را (و نه اعمال مجدد کل المانهای حاصل از متد render را).
این مورد را میتوان به نحو زیر آزمایش و مشاهده کرد:
در مرورگر بر روی المان span که شمارهها را نمایش میدهد، کلیک راست کرده و گزینهی inspect را انتخاب کنید. سپس بر روی دکمهی Increment کلیک نمائید. مرورگر قسمتی را که به روز میشود، با رنگی مشخص و متمایز، به صورت لحظهای نمایش میدهد:
ارسال پارامترها به متدهای رویدادگردان
تا اینجا متد handleIncrement، بدون پارامتر تعریف شدهاست. فرض کنید در یک برنامهی واقعی قرار است با کلیک بر روی این دکمه، id یک محصول را نیز به handleIncrement، منتقل و ارسال کنیم. اما در onClick={this.handleIncrement} تعریف شده، یک ارجاع را به متد handleIncrement داریم. بنابراین برای حل این مساله نمیتوان از روشی مانند onClick={this.handleIncrement(1)} استفاده کرد که در آن عدد فرضی 1 به صورت آرگومان متد handleIncrement ذکر شدهاست.
یک روش حل این مساله، تعریف متد دومی است که متد handleIncrement پارامتر دار را فراخوانی میکند:
doHandleIncrement = () => { this.handleIncrement({ id: 1, name: "Product 1" }); };
handleIncrement = product => { console.log(product); this.setState({ count: this.state.count + 1 }); };
هرچند این روش کار میکند، اما بیش از اندازه طولانی شدهاست. راه حل بهتر، استفاده از یک inline function است:
onClick={() => this.handleIncrement({ id: 1, name: "Product 1" })}
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-04-part02.zip