اشتراکها
اشتراکها
اصول کدنویسی JavaScript در سال 2015
اشتراکها
کنفرانس TechEd North America 2014
نظرات مطالب
تبدیل زیرنویسهای یوتیوب به فایل srt
https://www.dntips.ir/2010/12/subtitle-tools.html
در این قسمت میخواهیم انواع رویدادهای چرخهی حیات یک کامپوننت را بررسی کنیم. به همین جهت ابتدا دو کامپوننت جدید Lifecycle.razor و LifecycleChild.razor را به مثالی که تا این قسمت تکمیل کردهایم، اضافه کرده و آنهارا به صورت زیر جهت نمایش رویدادهای چرخهی حیات، تغییر میدهیم:
کدهای کامل کامپوننت Pages\LearnBlazor\Lifecycle.razor
و کدهای کامل کامپوننت Pages\LearnBlazor\LearnBlazorComponents\LifecycleChild.razor
و همچنین برای دسترسی به آنها، مدخل منوی زیر را به کامپوننت Shared\NavMenu.razor اضافه میکنیم:
با توجه به اینکه برنامهی جاری از نوع Blazor Server است، Console.WriteLineهای آن، در صفحهی کنسول اجرای برنامه ظاهر میشوند و نه در developer tools مرورگر:
رویدادهای OnInitialized و OnInitializedAsync
همانطور که در تصویر فوق نیز ملاحظه میکنید، اولین رویدادی که فراخوانی میشود، OnInitialized نام دارد و پس از آن نمونهی async آن به نام OnInitializedAsync. این رویدادها زمانیکه یک کامپوننت و اجزای UI آن کاملا بارگذاری شدهاند، فراخوانی میشوند. مهمترین کاربرد آنها، دریافت اطلاعات از سرویسهای برنامهاست.
در کامپوننت Lifecycle.razor، یک کامپوننت دیگر نیز به نام LifecycleChild.razor فراخوانی شدهاست. در این حالت ابتدا OnInitialized کامپوننت والد فراخوانی شدهاست و پس از آن بلافاصله فراخوانی OnInitialized کامپوننت فرزند را مشاهده میکنیم.
رویدادهای OnParametersSet و OnParametersSetAsync
این رویدادها یکبار در زمان بارگذاری اولیهی کامپوننت و بار دیگر هر زمانیکه کامپوننت فرزند، پارامتر جدیدی را از طریق کامپوننت والد دریافت میکند، فراخوانی میشوند. برای نمونه کامپوننت LifecycleChild، پارامتر CurrentCount را از والد خود دریافت میکند:
هرچند این پارامتر در UI کامپوننت فرزند مثال تهیه شده استفاده نمیشود، اما مقدار دهی آن از طرف والد، سبب بروز رویدادهای OnParametersSet و OnParametersSetAsync خواهد شد. برای آزمایش آن اگر بر روی دکمهی click me در کامپوننت والد کلیک کنیم، این رویدادهای جدید را مشاهده خواهیم کرد:
ابتدا متد IncrementCount کامپوننت والد فراخوانی شدهاست که سبب تغییر مقدار پارامتر CurrentCount ارسالی به کامپوننت LifecycleChild میشود و پس از آن، رویداد OnParameterSet کامپوننت فرزند را مشاهده میکنید که عکس العملی است به این تغییر مقدار. یکی از کاربردهای آن، دریافت مقدار جدید پارامترهای کامپوننت و سپس به روز رسانی قسمت خاصی از UI بر اساس آنها است.
رویدادهای OnAfterRender و OnAfterRenderAsync
پس از هر بار رندر کامپوننت، این متدها فراخوانی میشوند. در این مرحله کار بارگذاری کامپوننت، دریافت اطلاعات و نمایش آنها به پایان رسیدهاست. یکی از کاربردهای آن، آغاز کامپوننتهای جاوا اسکریپتی است که برای کار، نیاز به DOM را دارند؛ مانند نمایش یک modal بوت استرپی.
یک نکته: هر تغییری که در مقادیر فیلدها در این رویدادها صورت گیرند، به UI اعمال نمیشوند؛ چون در مرحلهی آخر رندر UI قرار دارند.
در مثالهای فوق، پارامتر 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، کدهای تایمر زیر تهیه شدهاند:
تایمر تعریف شده، یک thread timer است. یعنی callback آن بر روی یک ترد جدید و مجزای از ترد UI اجرا میشود. در این حالت اگر StateHasChanged را جهت اطلاع رسانی تغییر حالت UI فراخوانی نکنیم، در حین کار تایمر، هیچ تغییری را در UI مشاهده نخواهیم کرد.
یک نکته: متدهای رویدادگردان در Blazor، میتوانند sync و یا async باشند؛ مانند متدهای OnClick و OnClickAsync زیر که هر دو پس از پایان متدها، سبب فراخوانی ضمنی StateHasChanged نیز میشوند (به این دلیل است که با کلیک بر روی دکمهای، UI هم به روز رسانی میشود). البته متدهای رویدادگردان async، دوبار سبب فراخوانی ضمنی StateHasChanged میشوند؛ یکبار زمانیکه قسمت sync متد به پایان میرسد و یکبار هم زمانیکه کار فراخوانی کلی متد به پایان خواهد رسید:
بنابراین یکی دیگر از دلایل نیاز به فراخوانی صریح InvokeAsync(StateHasChanged) در callback تایمر تعریف شده، عدم فراخوانی خودکار آن، در پایان کار رویداد callback تایمر است.
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-06.zip
کدهای کامل کامپوننت 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\LearnBlazorComponents\LifecycleChild.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; } }
<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>
رویدادهای 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"); }
در کامپوننت Lifecycle.razor، یک کامپوننت دیگر نیز به نام LifecycleChild.razor فراخوانی شدهاست. در این حالت ابتدا OnInitialized کامپوننت والد فراخوانی شدهاست و پس از آن بلافاصله فراخوانی OnInitialized کامپوننت فرزند را مشاهده میکنیم.
رویدادهای OnParametersSet و OnParametersSetAsync
این رویدادها یکبار در زمان بارگذاری اولیهی کامپوننت و بار دیگر هر زمانیکه کامپوننت فرزند، پارامتر جدیدی را از طریق کامپوننت والد دریافت میکند، فراخوانی میشوند. برای نمونه کامپوننت LifecycleChild، پارامتر CurrentCount را از والد خود دریافت میکند:
<LifecycleChild CountValue="CurrentCount"></LifecycleChild>
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
رویدادهای 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"); } }
سؤال: با توجه به مقدار دهیهای 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); } } }
یک نکته: متدهای رویدادگردان در 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 }
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-06.zip