در اینجا React عنوان میکند که هر عنصر از لیستی را که در حال نمایش آن هستید، باید به همراه یک key، ارائه دهید. اما ... این key چیست؟
زمانیکه حالت کامپوننتی تغییر میکند (شیء یا اشیایی که به عناصر UI متصل هستند، تغییر میکنند)، React، درخت جدیدی از اشیایی را که باید رندر شوند، تولید میکند. اکنون React باید محاسبه کند که چه عناصری نسبت به درخت فعلی که در حال نمایش است، تغییر کردهاند تا فقط آنها را به DOM اصلی اعمال کند؛ تا این تغییرات به کاربر نمایش داده شوند و ... این کار را هم به خوبی انجام میدهد. پس مشکل با لیستها چیست که نیاز به key دارند؟
فرض کنید رندر لیستی، خروجی زیر را تولید میکند:
<li>Item 1</li> <li>Item 2</li>
<li>Item 1</li> <li>Item 2</li> <li>Item 3</li>
<li>Item 0</li> // <= New item <li>Item 1</li> <li>Item 2</li> <li>Item 3</li>
راهحل React برای تشخیص منحصربفرد بودن المانهای یک لیست و یا آرایه، استفاده از خاصیت key است:
<li key={0}>Item 0</li> // <= New item <li key={1}>Item 1</li> <li key={2}>Item 2</li> <li key={3}>Item 3</li>
این نکات چه ربطی به Blazor دارند؟!
واقعیت این است که Blazor، همان نسخهی مایکروسافتی React است (!) و این خاصیت key، در Blazor نیز تحت عنوان key directive@ وجود دارد و دقیقا مفهوم آن نیز با توضیحاتی که در مورد React داده شد، یکی است.
زمانیکه Blazor صفحهای را رندر میکند، ابتدا یک DOM مشخصی را تولید خواهد کد. سپس با تغییر State یک کامپوننت، DOM جدیدی را محاسبه کرده و آنرا با DOM فعلی مقایسه میکند و در نهایت diff تولیدی را به DOM موجود، در جهت نمایش تغییرات، اعمال خواهد کرد.
بنابراین الگوریتم diff باید اضافات، به روز رسانیها و حذفهای صورت گرفتهی در UI را تشخیص داده و فقط قسمتهای تغییر یافته را جهت به روز رسانی بهینهی UI، به آن اعمال کند. این الگوریتم diff به صورت پیشفرض از ایندکس المانها برای مقایسهی آنها استفاده میکند. هرچند این روش در بسیاری از حالات، بهینهترین روش است، اما در مورد لیستها خیر؛ که توضیحات آنرا با مثالی در مورد React، در ابتدای بحث بررسی کردیم. برای مثال اگر شیءای به انتهای لیست اضافه شود، هر المانی را که پس از این ایندکس قرار گرفته باشد، تغییر یافته درنظر گرفته و آنرا مجددا رندر میکند. به همین جهت است که اگر المانی به ابتدای یک لیست اضافه شود، اینبار کل لیست را مجددا رندر میکند (چون تمام ایندکسهای اشیاء موجود در لیست، تغییر کردهاند)؛ صرفنظر از اینکه عناصری از این لیست، پیشتر در UI رندر شدهاند و نیازی به رندر مجدد، ندارند.
یک مثال: بررسی نحوهی رندر لیستی از اشیاء در Blazor
در اینجا کدهای کامل کامپوننتی را مشاهده میکنید که یک لیست ساده را در ابتدا رندر کرده و هر بار که بر روی دکمهی افزودن یک شخص کلیک میشود، شخص جدیدی را به ابتدای لیست اضافه میکند:
@page "/" <button class="btn btn-primary" @onclick="addPerson">Add Person</button> <ul class="mt-5"> @foreach (var person in People) { <li>@person.Id, @person.Name</li> } </ul> @code{ List<Person> People = new() { new() { Id = 1, Name = "User 1" }, new() { Id = 2, Name = "User 2" }, }; int LastId = 2; void addPerson() { People.Insert(0, new() { Id = ++LastId, Name = $"User {LastId}" }); } class Person { public int Id { set; get; } public string Name { set; get; } } }
همانطور که مشاهده میکنید، در مثال فوق هر بار کلیک بر روی دکمهی افزودن یک شخص جدید، به همراه رندر تمام المانهای لیست است (محتوای تمام liها یکبار صورتی شده و بعد به حالت عادی در میآیند).
تغییر الگوریتم diff محاسبهی تغییرات UI
الگوریتم پیشفرض diff، از ایندکسهای عناصر برای یافتن تغییرات، استفاده میکند. با استفاده از دایرکتیو ویژهی key@ میتوان ایندکسهای پیشفرض را با مقادیری منحصربفرد، بازنویسی کرد، تا اینبار Blazor دقیقا بداند که کدام آیتم، جدید است و کدامها نیازی به رندر مجدد ندارند:
<ul class="mt-5"> @foreach (var person in People) { <li @key="person.Id">@person.Id, @person.Name</li> } </ul>
اگر به تصویر فوق دقت کنید، اینبار فقط li جدیدی که اضافه شدهاست، ابتدا با رنگ صورتی نمایش داده میشود و محتوای داخل سایر li ها، دست نخورده باقی ماندهاست؛ یعنی مجددا رندر نشدهاند.