環境 Blazor Net8.0 + FreeSql + Bootstrap Blazor 元件
以下都是自己瞎琢磨的和官網資料搬運,肯定有不少錯漏和不合理的地方,非常希望各位大佬評論區給我建議和意見.
1. 元件化需要提升渲染效能的元件,例如觸控式螢幕顯示每個商品下單數量的商品列表
避免不必要地呈現元件子樹, 執行一些初始化渲染後設定按需渲染, 外部控制按需渲染引數
//按需渲染
[Parameter]
public bool RenderQuantity { get; set; } = true;
protected override bool ShouldRender() => RenderQuantity;
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
//執行一些初始化渲染後設定按需渲染
你的初始化渲染();
StateHasChanged();
RenderQuantity = false;
}
}
protected override async Task OnParametersSetAsync()
{
if (RenderQuantity)
{
_ = Task.Run(async () =>
{
await Task.Delay(500);
RenderQuantity = false;
});
}
}
呼叫的頁面,新增 RenderQuantity 控制渲染數量變化時機. 避免 StateHasChanged() 執行後渲染子元件.
<OrdersMenuList OrderID="@OrderID"
RenderQuantity="@RenderQuantity"
Refresh="@RefreshOrdersMenuList"
/>
private bool RenderQuantity { get; set; }
2. API和UI分離: 例如更新訂單數量,操作記憶體資料渲染到UI,不要等待後臺查詢訂單詳單列表後在重新整理UI. 非同步執行後臺服務更新訂單數量.
UI 更新數量
Task UpdateQuantity(string userCode, int thisQuantity= 1)
{
//更新訂單數量,操作記憶體資料,不查詢資料庫,提高效能. API和UI分離
var itemOrdersMenu = OrdersMenu.Where(a => a.UserCode == userCode).FirstOrDefault();
if (itemOrdersMenu != null)
{
itemOrdersMenu.Quantity = thisQuantity;
RenderQuantity = true;
}
_ = Task.Run(async () =>
{
//更新訂單數量,返回合計
var newOrderdetailsDto = DataService.UpdateQuantity(userCode,thisQuantity);
if (newOrderdetailsDto.ForceQuantity!=null)
{
//處理髒資料問題,更新訂單數量為強制數量
item.Quantity = newOrderdetailsDto.ForceQuantity.Value;
if (itemOrdersMenu != null)
{
itemOrdersMenu.Quantity = newOrderdetailsDto.ForceQuantity;
RenderQuantity = true;
}
await InvokeAsync(StateHasChanged);
}
});
return Task.CompletedTask;
}
Tips: 對於長時間不操作的訂單介面,例如收銀臺桌面程式(Blazor/Blazor Hybird), 可以設定一個 UI 更新數量定時器, 例如間隔5分鐘重新重新整理整頁.
3. 髒資料: 因為是多終結點程式(PC瀏覽器/手機瀏覽器/PDA/桌面版),不可避免存在髒資料問題. 需要變更單行訂單數量重新整理UI後, 獲取後臺單行訂單數量,比對,有異常則重新執行 RenderQuantity 或 StateHasChanged 更新單行訂單數量.
4. 服務端不要直接更新訂單數量,改為原子操作, 採用 a.Quantity = a.Quantity + thisQuantity 方式
服務端 DataService.UpdateQuantity 方法:
fsql.Update<ResOrderDetails>()
.Set(a => new ResOrderDetails()
{
Quantity = a.Quantity + thisQuantity
})
.Where(a => a.OrderID == orderID && a.UserCode == userCode)
.ExecuteAffrows();
5. Button 儘可能使用 OnClickWithoutRender 方法: 點選按鈕時觸發此事件並且不重新整理當前元件,用於提高效能時使用.
6. 使用 CascadingValue 元件具有可選的 IsFixed 引數
-
如果 IsFixed 為 false(預設值),則級聯值的每個接收方都會將訂閱設定為接收更改通知。 由於訂閱跟蹤,每個 [CascadingParameter] 的開銷大體上都要比常規 [Parameter] 昂貴。
-
如果 IsFixed 為 true(例如,
),則接收方會接收初始值,但不會將訂閱設定為接收更新。 每個 [CascadingParameter] 都是輕型的,並不比常規 [Parameter] 昂貴。
如果有大量其他元件接收級聯值,則將 IsFixed 設定為 true 可提高效能。 只要有可能,就應將級聯值的 IsFixed 設定為 true。 當提供的值不會隨時間而改變時,可以將 IsFixed 設定為 true。
在元件將 this 作為級聯值傳遞時,也可以將 IsFixed 設定為 true:
<CascadingValue Value="this" IsFixed="true">
<SomeOtherComponents>
</CascadingValue>
7. 不要過快觸發事件
某些瀏覽器事件極頻繁地觸發。 例如,onmousemove 和 onscroll 每秒可以觸發數十或數百次。 在大多數情況下,不需要經常執行 UI 更新。 如果事件觸發速度過快,可能會損害 UI 響應能力或消耗過多的 CPU 時間。
請考慮使用 JS 互操作來註冊不太頻繁觸發的回撥,而不是使用快速觸發的本機事件。 例如,以下元件顯示滑鼠的位置,但每 500 毫秒最多隻能更新一次:
@implements IDisposable
@inject IJSRuntime JS
<h1>@message</h1>
<div @ref="mouseMoveElement" style="border:1px dashed red;height:200px;">
Move mouse here
</div>
@code {
private ElementReference mouseMoveElement;
private DotNetObjectReference<MyComponent>? selfReference;
private string message = "Move the mouse in the box";
[JSInvokable]
public void HandleMouseMove(int x, int y)
{
message = $"Mouse move at {x}, {y}";
StateHasChanged();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
selfReference = DotNetObjectReference.Create(this);
var minInterval = 500;
await JS.InvokeVoidAsync("onThrottledMouseMove",
mouseMoveElement, selfReference, minInterval);
}
}
public void Dispose() => selfReference?.Dispose();
}
相應的 JavaScript 程式碼會註冊用於滑鼠移動的 DOM 事件偵聽器。 在此示例中,事件偵聽器使用 Lodash 的 throttle 函式來限制呼叫速率:
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js"></script>
<script>
function onThrottledMouseMove(elem, component, interval) {
elem.addEventListener('mousemove', _.throttle(e => {
component.invokeMethodAsync('HandleMouseMove', e.offsetX, e.offsetY);
}, interval));
}
</script>
8. 使用快取
private ConcurrentDictionary<TabItem, bool> LazyTabCache { get; } = new();
private RenderFragment RenderTabItemContent(TabItem item) => builder =>
{
if (item.IsActive)
{
var content = _errorContent ?? item.ChildContent;
builder.AddContent(0, content);
_errorContent = null;
if (IsLazyLoadTabItem)
{
LazyTabCache.AddOrUpdate(item, _ => true, (_, _) => true);
}
}
else if (!IsLazyLoadTabItem || item.AlwaysLoad || LazyTabCache.TryGetValue(item, out var init) && init)
{
builder.AddContent(0, item.ChildContent);
}
};
private RenderFragment? _errorContent;
private static readonly ConcurrentDictionary<Type, UrlValueConstraint> _cachedInstances = new();
if (!_cachedInstances.TryGetValue(targetType, out result))
{
result = Create(targetType);
if (result is null)
{
return false;
}
_cachedInstances.TryAdd(targetType, result);
}
學習資料
Blazor 效能最佳做法
按需渲染,手動管理 UI 重新整理