بهینهسازی رندر در Blazor
نویسنده: وحید نصیری
تاریخ: ۱۴۰۴/۰۱/۲۰ ۰۸:۳۱
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
@inject NavigationManager NavigationManager
<select @onchange="OnValueChanged">
<option value="">انتخاب کنید</option>
@foreach (var item in Items)
{
<option value="@item">@item</option>
}
</select>
@code {
[Parameter] public List<string> Items { get; set; } = new();
[Parameter] public EventCallback<string> ValueChanged { get; set; }
private async Task OnValueChanged(ChangeEventArgs e)
{
string value = e.Value?.ToString() ?? "";
await ValueChanged.InvokeAsync(value);
NavigationManager.NavigateTo("/new-page");
}
}@onchange ، متد StateHasChanged را فراخوانی میکند تا مطمئن شود رابط کاربری بهروز است. این رفتار در کلاس پایه ComponentBase تعریف شده است. در مثال بالا، پس از اجرای OnValueChanged، Blazor یک رندر اضافی را آغاز میکند، حتی اگر هدایت (Navigation) به صفحه جدید بلافاصله انجام شود.protected override void OnInitialized()
{
Console.WriteLine("OnInitialized called");
}
protected override void OnParametersSet()
{
Console.WriteLine("OnParametersSet called");
}
protected override bool ShouldRender()
{
Console.WriteLine("ShouldRender called");
return true;
}private bool shouldRender = true;
protected override void OnParametersSet()
{
Console.WriteLine("OnParametersSet called");
shouldRender = true; // فقط وقتی پارامترها تغییر واقعی دارند
}
protected override bool ShouldRender()
{
Console.WriteLine("ShouldRender called");
if (!shouldRender)
{
return false;
}
shouldRender = false; // پس از اولین رندر، تا تغییر بعدی رندر نشود
return true;
}private bool isNavigating = false;
private async Task OnValueChanged(ChangeEventArgs e)
{
string value = e.Value?.ToString() ?? "";
await ValueChanged.InvokeAsync(value);
isNavigating = true;
NavigationManager.NavigateTo("/new-page");
}
protected override bool ShouldRender()
{
return !isNavigating;
}private async Task LoadDataAsync()
{
await Task.Delay(1000); // شبیهسازی دریافت داده
progress = 50;
StateHasChanged(); // رندر دستی در میانه کار
await Task.Delay(1000);
progress = 100;
}@rendermode InteractiveServer(false) // غیرفعال کردن پررندر
[HttpPost("send-message")]
public async Task<IActionResult> SendMessage()
{
for (int i = 0; i <= 100000; i++)
{
await _hubContext.Clients.All.SendAsync("ReceiveMessage",i );
}
return Ok();
}@using Microsoft.AspNetCore.SignalR.Client
@inject HubConnection HubConnection
<p @key="1"> <span>Message from server:</span> @model.LastOrDefault()</p>
@code {
private string message = "Waiting for messages...";
List<string> model = new List<string>();
protected override async Task OnInitializedAsync()
{
// متصل شدن به SignalR Hub
HubConnection.On<string>("ReceiveMessage", async (receivedMessage) =>
{
model.Add(receivedMessage);
await InvokeAsync(() =>
{
StateHasChanged();
});
});
await HubConnection.StartAsync();
}
}<script setup>
import WelcomeItem from './WelcomeItem.vue'
import DocumentationIcon from './icons/IconDocumentation.vue'
import ToolingIcon from './icons/IconTooling.vue'
import EcosystemIcon from './icons/IconEcosystem.vue'
import CommunityIcon from './icons/IconCommunity.vue'
import SupportIcon from './icons/IconSupport.vue'
</script>
<template>
<div>
<p>{{ lastItem }}</p>
</div>
</template>
<script>
// Import SignalR library
import * as signalR from '@microsoft/signalr';
export default {
data() {
return {
items: [],
message: 'Waiting for messages...',
connection: null, // The SignalR connection object
};
},
computed: {
lastItem() {
return this.items.length - 1;
}
},
mounted() {
// Create a new connection
this.connection = new signalR.HubConnectionBuilder()
.withUrl('http://localhost:5154/signalrhub') // URL of the SignalR hub (server-side)
.build();
// Start the connection
this.connection.start()
.then(() => {
console.log('Connected to SignalR Hub!');
})
.catch(err => {
console.error('Error while starting connection: ' + err);
});
// Listen for messages from the server
this.connection.on('ReceiveMessage', (message) => {
this.items.push(message);
});
},
beforeDestroy() {
// Stop the connection when the component is destroyed
if (this.connection) {
this.connection.stop();
}
}
};
</script>public async Task<IActionResult> SendMessage()
{
int batchSize = 100;
for (int i = 0; i <= 100000; i += batchSize)
{
var batch = Enumerable.Range(i, Math.Min(batchSize, 100001 - i)).ToList();
await _hubContext.Clients.All.SendAsync("ReceiveMessage", batch);
await Task.Delay(100); // تأخیر برای کاهش فشار
}
return Ok();
}StateHasChanged برای هر پیام، باعث رندر مکرر و کندی رابط کاربری میشود، بهویژه با حجم بالای پیامها (مهم!).private List<string> buffer = new List<string>();
private bool isRendering = false;
protected override async Task OnInitializedAsync()
{
HubConnection.On<string>("ReceiveMessage", async (receivedMessage) =>
{
buffer.Add(receivedMessage);
if (!isRendering)
{
isRendering = true;
await RenderBufferedMessages();
}
});
await HubConnection.StartAsync();
}
private async Task RenderBufferedMessages()
{
while (buffer.Any())
{
model.AddRange(buffer);
buffer.Clear();
await InvokeAsync(StateHasChanged);
await Task.Delay(500); // فرکانس رندر
}
isRendering = false;
}Virtualize استفاده کنید تا فقط پیامهای قابلمشاهده رندر شوند:<div style="height: 400px; overflow-y: auto;">
<Virtualize Items="model" Context="message">
<p>@message</p>
</Virtualize>
</div>model میتواند حافظه کلاینت را پر کند.private const int MaxMessages = 1000;
HubConnection.On<string>("ReceiveMessage", async (receivedMessage) =>
{
buffer.Add(receivedMessage);
if (model.Count >= MaxMessages)
{
model.RemoveRange(0, model.Count - MaxMessages + buffer.Count);
}
if (!isRendering)
{
isRendering = true;
await RenderBufferedMessages();
}
});List<string>، از Queue<string> برای مدیریت FIFO پیامها استفاده کنید.LastOrDefault)، یک لیست اسکرولشونده نمایش دهید.<div style="height: 400px; overflow-y: auto;">
<Virtualize Items="model" Context="message">
<p>@message</p>
</Virtualize>
</div>private string connectionStatus = "Connecting...";
protected override async Task OnInitializedAsync()
{
HubConnection.StateChanged += async (state) =>
{
connectionStatus = state.ToString();
await InvokeAsync(StateHasChanged);
};
// ... بقیه کدها
}HubConnection.Closed += async (error) =>
{
connectionStatus = "Disconnected. Reconnecting...";
await InvokeAsync(StateHasChanged);
await Task.Delay(5000);
await HubConnection.StartAsync();
};Clients.Group یا Clients.User استفاده کنید (مهم!).