Blazor 5x - قسمت ششم - مبانی Blazor - بخش 3 - چرخه‌های حیات کامپوننت‌ها
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: نه دقیقه

در این قسمت می‌خواهیم انواع رویدادهای چرخه‌ی حیات یک کامپوننت را بررسی کنیم. به همین جهت ابتدا دو کامپوننت جدید Lifecycle.razor و Lifecycle‍Child.razor را به مثالی که تا این قسمت تکمیل کرده‌ایم، اضافه کرده و آن‌ها‌را به صورت زیر جهت نمایش رویدادهای چرخه‌ی حیات، تغییر می‌دهیم:

کدهای کامل کامپوننت Pages\LearnBlazor\Lifecycle.razor
@page "/lifecycle"
@using System.Threading

<div class="border">
    <h3>Lifecycles Parent Component</h3>

    <div class="border">
        <LifecycleChild CountValue="CurrentCount"></LifecycleChild>
    </div>

    <p>Current count: @CurrentCount</p>

    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
    <br /><br />
    <button class="btn btn-primary" @onclick=StartCountdown>Start Countdown</button> @MaxCount
</div>

@code
{
    int CurrentCount = 0;
    int MaxCount = 5;

    private void IncrementCount()
    {
        CurrentCount++;
        Console.WriteLine("Parnet - IncrementCount is called");
    }

    protected override void OnInitialized()
    {
        Console.WriteLine("Parnet - OnInitialized is called");
    }

    protected override async Task OnInitializedAsync()
    {
        await Task.Delay(100);
        Console.WriteLine("Parnet - OnInitializedAsync is called");
    }

    protected override void OnParametersSet()
    {
        Console.WriteLine("Parnet - OnParameterSet is called");
    }

    protected override async Task OnParametersSetAsync()
    {
        await Task.Delay(100);
        Console.WriteLine("Parnet - OnParametersSetAsync is called");
    }

    protected override void OnAfterRender(bool firstRender)
    {
        if (firstRender)
        {
            Console.WriteLine("Parnet - OnAfterRender(firstRender == true) is called");
            CurrentCount = 111;
        }
        else
        {
            CurrentCount = 999;
            Console.WriteLine("Parnet - OnAfterRender(firstRender == false) is called");
        }
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        await Task.Delay(100);
        Console.WriteLine("Parnet - OnAfterRenderAsync is called");
    }

    protected override bool ShouldRender()
    {
        Console.WriteLine("Parnet - ShouldRender is called");
        return true;
    }

    void StartCountdown()
    {
        Console.WriteLine("Parnet - StartCountdown()");
        var timer = new Timer(TimeCallBack, null, 1000, 1000);
    }

    void TimeCallBack(object state)
    {
        if (MaxCount > 0)
        {
            MaxCount--;
            Console.WriteLine("Parnet - InvokeAsync(StateHasChanged)");
            InvokeAsync(StateHasChanged);
        }
    }
}

و کدهای کامل کامپوننت Pages\LearnBlazor\LearnBlazor‍Components\Lifecycle‍Child.razor
<h3 class="ml-3 mr-3">Lifecycles Child Componenet</h3>

@code
{
    [Parameter]
    public int CountValue { get; set; }

    protected override void OnInitialized()
    {
        Console.WriteLine("  Child - OnInitialized is called");
    }

    protected override async Task OnInitializedAsync()
    {
        await Task.Delay(100);
        Console.WriteLine("  Child - OnInitializedAsync is called");
    }

    protected override void OnParametersSet()
    {
        Console.WriteLine("  Child - OnParameterSet is called");
    }

    protected override async Task OnParametersSetAsync()
    {
        await Task.Delay(100);
        Console.WriteLine("  Child - OnParametersSetAsync is called");
    }

    protected override void OnAfterRender(bool firstRender)
    {
        if (firstRender)
        {
            Console.WriteLine("  Child - OnAfterRender(firstRender == true) is called");
        }
        else
        {
            Console.WriteLine("  Child - OnAfterRender(firstRender == false) is called");
        }
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        await Task.Delay(100);
        Console.WriteLine("  Child - OnAfterRenderAsync is called");
    }

    protected override bool ShouldRender()
    {
        Console.WriteLine("  Child - ShouldRender is called");
        return true;
    }
}
و همچنین برای دسترسی به آن‌ها، مدخل منوی زیر را به کامپوننت Shared\NavMenu.razor اضافه می‌کنیم:
<li class="nav-item px-3">
    <NavLink class="nav-link" href="lifecycle">
        <span class="oi oi-list-rich" aria-hidden="true"></span> Lifecycles
    </NavLink>
</li>
با توجه به اینکه برنامه‌ی جاری از نوع Blazor Server است، Console.WriteLine‌های آن، در صفحه‌ی کنسول اجرای برنامه ظاهر می‌شوند و نه در developer tools مرورگر:





رویدادهای OnInitialized و OnInitializedAsync

@code
{
    protected override void OnInitialized()
    {
        Console.WriteLine("Parnet - OnInitialized is called");
    }

    protected override async Task OnInitializedAsync()
    {
        await Task.Delay(100);
        Console.WriteLine("Parnet - OnInitializedAsync is called");
    }
همانطور که در تصویر فوق نیز ملاحظه می‌کنید، اولین رویدادی که فراخوانی می‌شود، OnInitialized نام دارد و پس از آن نمونه‌ی async آن به نام OnInitializedAsync. این رویدادها زمانیکه یک کامپوننت و اجزای UI آن کاملا بارگذاری شده‌اند، فراخوانی می‌شوند. مهم‌ترین کاربرد آن‌ها، دریافت اطلاعات از سرویس‌های برنامه‌است.
در کامپوننت Lifecycle.razor، یک کامپوننت دیگر نیز به نام Lifecycle‍Child.razor فراخوانی شده‌است. در این حالت ابتدا OnInitialized کامپوننت والد فراخوانی شده‌است و پس از آن بلافاصله فراخوانی OnInitialized کامپوننت فرزند را مشاهده می‌کنیم.


رویدادهای OnParametersSet و OnParametersSetAsync

این رویدادها یکبار در زمان بارگذاری اولیه‌ی کامپوننت و بار دیگر هر زمانیکه کامپوننت فرزند، پارامتر جدیدی را از طریق کامپوننت والد دریافت می‌کند، فراخوانی می‌شوند. برای نمونه کامپوننت LifecycleChild، پارامتر CurrentCount را از والد خود دریافت می‌کند:
<LifecycleChild CountValue="CurrentCount"></LifecycleChild>
هرچند این پارامتر در UI کامپوننت فرزند مثال تهیه شده استفاده نمی‌شود، اما مقدار دهی آن از طرف والد، سبب بروز رویدادهای OnParametersSet و OnParametersSetAsync خواهد شد. برای آزمایش آن اگر بر روی دکمه‌ی click me در کامپوننت والد کلیک کنیم، این رویدادهای جدید را مشاهده خواهیم کرد:
Parnet - IncrementCount is called
Parnet - ShouldRender is called
  Child - OnParameterSet is called
  Child - ShouldRender is called
Parnet - OnAfterRender(firstRender == false) is called
  Child - OnAfterRender(firstRender == false) is called
  Child - OnParametersSetAsync is called
  Child - ShouldRender is called
  Child - OnAfterRender(firstRender == false) is called
  Child - OnAfterRenderAsync is called
Parnet - OnAfterRenderAsync is called
  Child - OnAfterRenderAsync is called
ابتدا متد IncrementCount کامپوننت والد فراخوانی شده‌است که سبب تغییر مقدار پارامتر CurrentCount ارسالی به کامپوننت Lifecycle‍Child می‌شود و پس از آن، رویداد OnParameterSet کامپوننت فرزند را مشاهده می‌کنید که عکس العملی است به این تغییر مقدار. یکی از کاربردهای آن، دریافت مقدار جدید پارامترهای کامپوننت و سپس به روز رسانی قسمت خاصی از UI بر اساس آن‌ها است.



رویدادهای OnAfterRender و OnAfterRenderAsync

پس از هر بار رندر کامپوننت، این متدها فراخوانی می‌شوند. در این مرحله کار بارگذاری کامپوننت، دریافت اطلاعات و نمایش آن‌ها به پایان رسیده‌است. یکی از کاربردهای آن، آغاز کامپوننت‌های جاوا اسکریپتی است که برای کار، نیاز به DOM را دارند؛ مانند نمایش یک modal بوت استرپی.

یک نکته: هر تغییری که در مقادیر فیلدها در این رویدادها صورت گیرند، به UI اعمال نمی‌شوند؛ چون در مرحله‌ی آخر رندر UI قرار دارند.

@code
{
    protected override void OnAfterRender(bool firstRender)
    {
        if (firstRender)
        {
            Console.WriteLine("Parnet - OnAfterRender(firstRender == true) is called");
            CurrentCount = 111;
        }
        else
        {
            CurrentCount = 999;
            Console.WriteLine("Parnet - OnAfterRender(firstRender == false) is called");
        }
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        await Task.Delay(100);
        Console.WriteLine("Parnet - OnAfterRenderAsync is called");
    }
}
در مثال‌های فوق، پارامتر firstRender را نیز مشاهده می‌کنید. یک کامپوننت چندین بار می‌تواند رندر شود. برای مثال هربار که توسط رویدادگردانی مقدار فیلدی را که در UI استفاده می‌شود، تغییر دهیم، کامپوننت مجددا رندر می‌شود. برای نمونه با کلیک بر روی دکمه‌ی click me، سبب تغییر مقدار فیلد CurrentCount می‌شویم. این تغییر و فراخوانی ضمنی StateHasChanged در پایان کار متد و در پشت صحنه، سبب رندر مجدد UI شده و در نتیجه‌ی آن، مقدار جدیدی را در صفحه مشاهده می‌کنیم. در اینجا اگر خواستیم بدانیم که رندر انجام شده برای بار اول است که صورت می‌گیرد یا خیر، می‌توان از پارامتر firstRender استفاده کرد.

سؤال: با توجه به مقدار دهی‌های 111 و 999 صورت گرفته‌ی در متد OnAfterRender، در اولین بار نمایش کامپوننت، چه عددی به عنوان CurrentCount نمایش داده می‌شود؟
در اولین بار نمایش صفحه، لحظه‌ای عدد 111 و سپس عدد 999 نمایش داده می‌شود. عدد 111 را در بار اول رندر و عدد 999 را در بار دوم رندر که پس از مقدار دهی پارامتر کامپوننت فرزند است، می‌توان مشاهده کرد.
اما ... اگر پس از نمایش اولیه‌ی صفحه، چندین بار بر روی دکمه‌ی click me کلیک کنیم، همواره عدد 1000 مشاهده می‌شود. علت اینجا است که تغییرات مقادیر فیلدها در متد OnAfterRender، به UI اعمال نمی‌شوند؛ چون در این مرحله، رندر UI به پایان رسیده‌است. در اینجا فقط مقدار فیلد CurrentCount به 999 تغییر می‌کند و به همین صورت باقی می‌ماند. دفعه‌ی بعدی که بر روی دکمه‌ی click me کلیک می‌کنیم، یک واحد به آن اضافه شده و اکنون است که کار رندر UI، مجددا شروع خواهد شد (در واکشن به یک رخ‌داد و فراخوانی ضمنی StateHasChanged در پشت صحنه) و اینبار حاصل 999+1 را در UI مشاهده می‌کنیم و باز هم در پایان کار رندر، مجددا مقدار CurrentCount به 999 تغییر می‌کند که ... دیگر به UI منعکس نمی‌شود تا زمان کلیک بعدی و همینطور الی آخر.


رویدادهای StateHasChanged و ShouldRender

- اگر خروجی رویداد ShouldRender مساوی true باشد، اجازه‌ی اعمال تغییرات به UI داده خواهد شد و برعکس. بنابراین اگر حالت UI تغییر کند و خروجی این متد false باشد، این تغییرات نمایش داده نخواهند شد.
- اگر رویداد StateHasChanged فراخوانی شود، به معنای درخواست رندر مجدد UI است. کاربرد آن در مکان‌هایی است که نیاز به اطلاع رسانی دستی تغییرات UI وجود دارد؛ درست پس از زمانیکه رندر UI به پایان رسیده‌است. برای آزمایش این مورد و فراخوانی دستی StateHasChanged، کدهای تایمر زیر تهیه شده‌اند:
@page "/lifecycle"
@using System.Threading

button class="btn btn-primary" @onclick=StartCountdown>Start Countdown</button> @MaxCount

@code
{
    int MaxCount = 5;

    void StartCountdown()
    {
        Console.WriteLine("Parnet - StartCountdown()");
        var timer = new Timer(TimeCallBack, null, 1000, 1000);
    }

    void TimeCallBack(object state)
    {
        if (MaxCount > 0)
        {
            MaxCount--;
            Console.WriteLine("Parnet - InvokeAsync(StateHasChanged)");
            InvokeAsync(StateHasChanged);
        }
    }
}
تایمر تعریف شده، یک thread timer است. یعنی callback آن بر روی یک ترد جدید و مجزای از ترد UI اجرا می‌شود. در این حالت اگر StateHasChanged را جهت اطلاع رسانی تغییر حالت UI فراخوانی نکنیم، در حین کار تایمر، هیچ تغییری را در UI مشاهده نخواهیم کرد.


یک نکته: متدهای رویدادگردان در Blazor، می‌توانند sync و یا async باشند؛ مانند متدهای OnClick و OnClickAsync زیر که هر دو پس از پایان متدها، سبب فراخوانی ضمنی StateHasChanged نیز می‌شوند (به این دلیل است که با کلیک بر روی دکمه‌ای، UI هم به روز رسانی می‌شود). البته متدهای رویدادگردان async، دوبار سبب فراخوانی ضمنی StateHasChanged می‌شوند؛ یکبار زمانیکه قسمت sync متد به پایان می‌رسد و یکبار هم زمانیکه کار فراخوانی کلی متد به پایان خواهد رسید:
<button @onclick="OnClick">Synchronous</button>
<button @onclick="OnClickAsync">Asynchronous</button>
@code{
    void OnClick()
    {
    } // StateHasChanged is called after the method

    async Task OnClickAsync()
    {
        text = "click1";
        // StateHasChanged is called here as the synchronous part of the method ends

        await Task.Delay(1000);
        await Task.Delay(2000);
        text = "click2";
    } // StateHasChanged is called after the method
}
بنابراین یکی دیگر از دلایل نیاز به فراخوانی صریح InvokeAsync(StateHasChanged) در callback تایمر تعریف شده، عدم فراخوانی خودکار آن، در پایان کار رویداد callback تایمر است.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-06.zip
  • #
    ‫۳ سال و ۶ ماه قبل، چهارشنبه ۱۳ اسفند ۱۳۹۹، ساعت ۱۷:۴۶
    ممنون از مطلب مفیدتون.
    خواستم بدونم برای پیاده سازی‌های کلاینت ساید نیازه از متد‌های Async استفاده کنیم؟
    • #
      ‫۳ سال و ۶ ماه قبل، چهارشنبه ۱۳ اسفند ۱۳۹۹، ساعت ۱۷:۵۲
      - بله؛ در جائی که قرار هست ترد UI را خالی کنید، مانند مثال «نمایش شرطی عبارات در فایل‌های razor.» که مربوط به دریافت اطلاعات از سرویس‌های از راه دور است یا ارسال اطلاعات به آن‌ها (که دقیقا معادل اعمال Ajax ای سمت کلاینت هستند)، «نیاز» هست از متدهای async استفاده کنید. علت وجودی دو نگارش sync و async هم در اینجا به ازای هر رویدادگران تعریف شده، به همین دلیل است. متدهای async سمت کلاینت، فقط ترد UI را خالی می‌کنند، تا UI برنامه در طی این متد رفت و برگشتی به سمت سرور پاسخگو باشد و هنگ کرده به نظر نرسد.
      - اینکه چه متدی را باید async تعریف کرد یا نه، مطالعه‌ی مطلب «متدهای async تقلبی» حتی در اینجا هم مفید است.
  • #
    ‫۳ سال و ۴ ماه قبل، دوشنبه ۶ اردیبهشت ۱۴۰۰، ساعت ۱۵:۳۲
    یک نکته‌ی تکمیلی: تنظیم پویای عنوان صفحات در برنامه‌های Blazor

    برای تنظیم پویای عنوان یک صفحه‌ی وب، نیاز است با DOM API مرورگر به صورت مستقیم کار کرد. برای مثال فایل wwwroot\main.js را که مدخل آن به کامپوننت Host_ و یا صفحه‌ی index.html اضافه می‌شود، به صورت زیر تکمیل می‌کنیم:
    window.JsFunctionHelper = {
      blazorSetTitle: function (title) {
        document.title = title;
      }
    };
    اکنون می‌خواهیم این متد جاوااسکریپتی را که مستقیما با شیء document کار می‌کند، در کامپوننت جدید Client\Shared\PageTitle.razor استفاده کنیم:
    @inject IJSRuntime JSRuntime
    
    @code
    {
       [Parameter]
       public string Title { get; set; }
    
       protected override async Task OnParametersSetAsync()
       {
          await JSRuntime.InvokeVoidAsync("JsFunctionHelper.blazorSetTitle", Title);
       }
    }
    در اینجا کامپوننت جدیدی تعریف شده‌است که به محض تنظیم مقدار پارامتر عنوان آن، سبب فراخوانی متد جاوا اسکریپتی blazorSetTitle می‌شود. برای نمونه روش استفاده‌ی از آن در کامپوننت Counter، جهت نمایش عنوانی پویا، به محض تغییر مقدار شمارشگر، به صورت زیر می‌تواند باشد:
    @page "/counter"
    
    <PageTitle Title="@GetPageTitle()" />
    
    <h1>Counter</h1>
    
    <p>Current count: @currentCount</p>
    
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
    
    @code {
        private int currentCount = 0;
    
        private void IncrementCount()
        {
            currentCount++;
        }
    
        private string GetPageTitle() => $"Counter ({currentCount})";
    }
    این روش در برنامه‌های Blazor Server کار نخواهد کرد و در حین فراخوانی متد InvokeVoidAsync یک NullReferenceException مشاهده می‌شود؛ چون این نوع برنامه‌های Blazor Server به همراه یک مرحله‌ی pre-render در سمت سرور هستند که ابتدا، کار تهیه‌ی HTML ای را که باید به سمت مرورگر ارسال کنند، به پایان می‌رسانند. در این مرحله خبری از DOM نیست که بتوان به آن دسترسی یافت و تغییری را در آن ایجاد کرد.
    برای رفع این مشکل همانطور که در مطلب جاری نیز عنوان شد، باید از روال رویدادگردان OnAfterRenderAsync استفاده کرد. در این حالت کدهای کامپوننت PageTitle.razor به صورت زیر تغییر می‌کنند:
    @inject IJSRuntime JSRuntime
    
    @code
    {
       [Parameter]
       public string Title { get; set; }
    
       protected override async Task OnAfterRenderAsync(bool firstRender)
       {
          await JSRuntime.InvokeVoidAsync("JsFunctionHelper.blazorSetTitle", Title);
       }
    }
    روال رویدادگردان OnAfterRenderAsync پس از اینکه کار بارگذاری و تشکیل کامل DOM در مرورگر انجام شد، فراخوانی می‌شود. به همین جهت دیگر دسترسی به شیء document.title، سبب بروز یک NullReferenceException نخواهد شد.


    یک نکته: قرار است در Blazor 6x، کامپوننت‌های جدید Title، Link و Meta جهت تنظیم اطلاعات تگ head صفحه، به صورت استاندارد اضافه شوند:
    <Title Value="@title" />
    <Meta name="description" content="Modifying the head from a Blazor component." />
    <Link href="main.css" rel="stylesheet" />
    • #
      ‫۲ سال و ۹ ماه قبل، یکشنبه ۱۶ آبان ۱۴۰۰، ساعت ۱۴:۵۲
      بهبودهای Blazor 6x جهت تنظیم محتوای head صفحات

      در اینجا پیشتر روشی را مبتنی بر جاوا اسکریپت جهت تنظیم عنوان صفحات مشاهده کردید. در Blazor 6x دیگر به این راه حل نیازی نیست.

      کامپوننت جدید PageTitle 
      @page "/counter"
      <PageTitle>Counter</PageTitle>
      با استفاده از این کامپوننت که آن‌را می‌توان در هر قسمتی، قرار داد، امکان به روز رسانی (حتی پویای) عنوان صفحه، وجود دارد. این کامپوننت در فضای نام Microsoft.AspNetCore.Components.Web قرار دارد و اگر بیش از یک PageTitle در یک کامپوننت تعریف شود، آخرین مورد آن پردازش خواهد شد.

      کامپوننت جدید HeadContent
      @page "/counter"
      <PageTitle>Counter</PageTitle>
      <HeadContent>
        <meta name="description" content="Use this page to count things!" />
        <meta name="author" content="VahidN">
        <link rel="icon" href="favicon.ico" type="image/x-icon">
        <link rel="sitemap" type="application/xml" title="Sitemap" href="@(NavigationManager.BaseUri)sitemap.xml" />
        <link rel="alternate" type="application/rss+xml" href="@(NavigationManager.BaseUri)atom.xml">
        <link rel="canonical" href="@(NavigationManager.BaseUri)good-content" />
        <meta name="robots" content="index, follow" />
      </HeadContent>
      هدف از این کامپوننت جدید، تنظیم پویای محتوای تگ استاندارد head صفحه‌ی HTML نهایی است که در اینجا برای نمونه، چند تگ مخصوص SEO را به head اضافه می‌کند. همچنین باید دقت داشت که اگر بیش از یک HeadContent را تعریف کنیم، فقط آخرین مورد پردازش می‌شود.
      یک نکته: در اینجا هم می‌توان تگ استاندارد title را اضافه کرد. اما باید دقت داشت که در این حالت، صرفا کار افزودن این تگ صورت می‌گیرد؛ بدون توجه به وجود کامپوننت PageTitle تعریف شده. یعنی بیش از یک title در تگ head درج می‌شود که یک HTML غیرمعتبر به حساب خواهد آمد.

      کامپوننت جدید HeadOutlet
      این کامپوننت، کار پردازش دو کامپوننت یاد شده را انجام می‌دهد و محل تعریف آن، در فایل Program.cs است که در قالب پروژه‌های جدید Blazor 6x، به صورت خودکار، درج و تنظیم شده‌است:
      var builder = WebAssemblyHostBuilder.CreateDefault(args);
      builder.RootComponents.Add<App>("#app");
      builder.RootComponents.Add<HeadOutlet>("head::after");
      head::after در CSS استاندارد به معنای درج آن به عنوان آخرین فرزند گره انتخابی (head در اینجا) است.

  • #
    ‫۳ سال و ۴ ماه قبل، سه‌شنبه ۱۴ اردیبهشت ۱۴۰۰، ساعت ۲۲:۲۱
    دلیل دوبار فراخوانی کدهای Razor در هنگام استفاده  از متدهای async سرویس‌های EF چیست ؟آیا راهی برای جلوگیری از تکرار آن وجود دارد؟
    در تکه کد زیر کلمه Code دوبار در خروجی چاپ میشود و این برای کار با حلقه ها  باعث کاهش کارایی میشود
    @page "/counter"  
    @{ Debug.WriteLine("Code"); } @code { protected override async Task OnInitializedAsync() { var _dbContext = new ApplicationDbContext(); await _dbContext.Question.ToListAsync(); } }
    • #
      ‫۳ سال و ۴ ماه قبل، چهارشنبه ۱۵ اردیبهشت ۱۴۰۰، ساعت ۰۰:۵۱
      چه زمانی کامپوننت‌ها رندر می‌شوند؟
      - زمانیکه برای اولین بار ایجاد شده و نمایش داده می‌شوند.
      - زمانیکه رخدادی صورت گرفته و مدیریت می‌شود.
      - زمانیکه مقادیر پارامترهای یک کامپوننت تغییر می‌کنند.
      - زمانیکه برنامه نویس به صورت دستی متد StateHasChanged را فراخوانی کند.
       
      بنابراین در مثال شما، یکبار جهت اولین بار نمایش کامپوننت، کدهای Razor آن رندر می‌شوند؛ یکبار هم پس از پایان یک روال رویدادگردان. همچنین نکته‌ی async انتهای بحث را هم مطالعه کنید.
    • #
      ‫۳ سال و ۳ ماه قبل، پنجشنبه ۱۶ اردیبهشت ۱۴۰۰، ساعت ۱۵:۵۹
      یک نکته‌ی تکمیلی: امکان اجرای دوباره  OnInitialized{Async} در برنامه‌های Blazor Server
      در برنامه‌های Blazor Server اگر حالت رندر شدن به Server Prerendered تنظیم شده باشد، کامپوننت ابتدا یکبار به صورت استاتیک رندر می‌شود و زمانیکه اتصال SignalR برقرار می‌شود، بار دیگر نیز رندر خواهد شد (هدف این است که پیش از دریافت کامل برنامه‌ی Blazor بتوان محتوایی را نمایش داد). بنابراین در این حالت متد رویدادگردان OnInitialized{Async} حتما دوبار اجرا می‌شود (ماخذ).
      اگر می‌خواهید این رفتار را تغییر دهید، به فایل Host.cshtml_ برنامه‌های Blazor Server مراجعه کرده و "render-mode="ServerPrerendered پیش‌فرض را به "render-mode="Server تغییر دهید؛ یا کدهای OnInit را به OnAfterRenderAsync انتقال داده و وضعیت وجود اتصال SignalR را بررسی کنید:
      @page "/"
      @using Microsoft.JSInterop
      @inject IComponentContext ComponentContext
      @inject IJSRuntime jsRuntime
      
      <p>Navigate to the counter component.</p>
      
      @code{
      
          protected override async Task OnAfterRenderAsync(bool firstRender)
          {
              if (!ComponentContext.IsConnected) return;
      
               UriHelper.NavigateTo("/counter");
          }
      }
      • #
        ‫۲ سال و ۲ ماه قبل، دوشنبه ۶ تیر ۱۴۰۱، ساعت ۰۰:۱۷
        یک نکته‌ی تکمیلی: روش جلوگیری از رندر مجدد کامپوننت‌ها
        یکی از مواردی که در Blazor کارآیی را کاهش میدهد، رندر مجدد کامپوننتها بعد از فراخوانی رویدادی میباشد. به صورت پیش‌فرض، و به‌طور خودکار پس از فراخوانی یک رویداد، بلافاصله StateHasChanged کامپوننت فراخوانی می‌شود. در برخی موارد، ممکن است اجرای StateHasChanged غیرضروری باشد. بخصوص برای صفحاتی که تعداد زیادی المنت در آن قرار داشته باشد. این رندر مجدد باعث کاهش سرعت بروزرسانی UI میشود. جهت جلوگیری از اجرای خودکار StateHasChanged میتوانیم اینترفیس   IHandleEvent  را پیاده سازی کنیم که بصورت سراسری بر تمام کامپوننتهای یک صفحه تاثیر بگذارد.
        @implements IHandleEvent
        @code {
                Task IHandleEvent.HandleEventAsync(EventCallbackWorkItem callback, object? arg) => callback.InvokeAsync(arg);
        }
         در اینگونه موارد میتوانیم UI را بصورت دستی با فراخوانی StateHasChanged   به‌روز رسانی کنیم.
        • #
          ‫۲ سال و ۲ ماه قبل، دوشنبه ۶ تیر ۱۴۰۱، ساعت ۰۳:۲۳
          و یا می‌توان در هر کامپوننتی، متد ShouldRender را به صورت زیر بازنویسی کرد:
          protected override bool ShouldRender()
          {
            return shouldRender;
          }
          این متد اگر false برگرداند، رندر مجددی انجام نخواهد شد و امکان کنترل بیشتری را جهت رندر کلی UI فراهم می‌کند.
  • #
    ‫۲ سال و ۲ ماه قبل، سه‌شنبه ۷ تیر ۱۴۰۱، ساعت ۲۲:۵۰
    در react هوکی با نام componentDidUpdate وجود داشت که تغییر state را اعلام میکرد و با یک بررسی متوجه اینکه چه چیزی تغییر کرده بود میشدیم. آیا در رویدادی مثل ParameterSet این امکان وجود ندارد که بتوان متوجه شد چه چیزی تغییر کرده‌است تا متناسب با آن تغییر کدهای مورد نظر اجرا گردند؟