目录
介绍
代码
组件渲染
Blazor UI事件
Blazor UI事件模式
总结
附录
介绍对于刚接触Blazor的程序员来说,最常见的问题之一是UI事件和相关的渲染过程。此类问题每天都会发布在StackOverflow等网站上。希望这篇文章能消除一些疑惑!
代码本文没有代码库。在本文的附录中有一个单页演示Razor文件,您可以使用它进行测试。
渲染片段
什么是RenderFragment?
对于许多人来说,它看起来像这样:
Hello World
string——Razor中的标记块。
深入到DotNetCore代码库,你会发现:
public delegate void RenderFragment(RenderTreeBuilder builder);
如果您不完全理解委托,请将其视为一种模式。任何符合模式的函数都可以作为RenderFragment。
该模式规定您的方法必须:
- 有一个,而且只有一个,类型为RenderTreeBuilder的参数。
- 返回一个void。
让我们看一个例子:
protected void BuildHelloWorld(RenderTreeBuilder builder)
{
builder.OpenElement(0, "div");
builder.AddContent(1, "Hello World");
builder.CloseElement();
}
我们可以将其重写为属性:
protected RenderFragment HelloWorldFragment => (RenderTreeBuilder builder) =>
{
builder.OpenElement(0, "div");
builder.AddContent(1, "Hello World");
builder.CloseElement();
};
或者:
protected RenderFragment HelloWorldFragment => (builder) =>
{
builder.OpenElement(0, "div");
builder.AddContent(1, "Hello World");
builder.CloseElement();
};
当一个Razor文件被编译时,它被Razor编译器转换成一个C#类文件。
ADiv.razor组件:
Hello World
被编译成:
namespace Blazr.UIDemo.Pages
{
public partial class ADiv : Microsoft.AspNetCore.Components.ComponentBase
{
protected override void BuildRenderTree
(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
__builder.AddMarkupContent(0, "\r\n Hello World\r\n");
}
}
}
Razor页面/组件的基本组件是ComponentBase.。这个类有一个public方法StateHasChanged来渲染组件。
一个常见的问题代码片段:
void ButtonClick()
{
// set some message saying processing
StateHasChanged();
// Do some work
// set some message saying complete
StateHasChanged();
}
出现的唯一消息是完整的。为什么?第一个StateHasChanged不是在调用“Do Some Work”之前重新渲染组件吗?
是的,StateHasChanged确实运行了。但是,要理解这个问题,我们需要仔细查看StateHasChanged和组件渲染片段的缩写版本。
protected void StateHasChanged()
{
if (_hasPendingQueuedRender)
return;
else
{
_hasPendingQueuedRender = true;
_renderHandle.Render(_renderFragment);
}
}
_renderFragment = builder =>
{
_hasPendingQueuedRender = false;
BuildRenderTree(builder);
};
首先,它检查渲染是否已经排队——_hasPendingQueuedRender是false。如果不是,则设置_hasPendingQueuedRender为true并调用_renderHandle.Render传递它_renderFragment(组件的渲染片段)。就是这样。
_hasPendingQueuedRender实际运行渲染片段时设置为false。对于好奇的人,_renderHandle在被附加到RenderTree(Renderer调用Attach)是别传递给组件。
需要理解的重要一点是,StateHasChanged将组件渲染片段_renderFragment作为 要给delegate队列放到Renderer的渲染队列上。它不执行渲染片段。那是Renderer的工作。
如果我们回到按钮点击,它是在UI线程上运行的所有顺序同步代码。renderer不运行——从而服务于它的渲染队列——直到ButtonClick完成。没有屈服。
Blazor UI事件让我们看另一个常见问题来理解UI事件流程:
async void ButtonClick()
{
// set some message saying processing
// Call Task.Wait to simulate some yielding async work
await Task.Wait(1000);
// set some message saying complete
}
为什么我们只看到第一条消息?在代码末尾添加一个StateHasChanged,它就可以工作了。
async void ButtonClick()
{
// set some message saying processing
// Call Task.Wait to simulate some yielding async work
await Task.Wait(1000);
// set some message saying complete
StateHasChanged();
}
您可能已经解决了显示问题,但您还没有解决问题。
Blazor UI事件模式Blazor UI事件不是一劳永逸的。使用的基本模式是:
var task = InvokeAsync(EventMethod);
StateHasChanged();
if (!task.IsCompleted)
{
await task;
StateHasChanged();
}
我们的按钮事件得到了一个Task包装器 task。它要么运行到一个yield事件,要么运行到完成。此时,StateHasChanged被调用并且渲染事件排队并执行。如果task尚未完成,则处理程序等待任务,并在完成时调用StateHasChanged。
ButtonClick的问题是它产生了,但是给事件处理程序传递一个void,事件处理程序没有什么可等待的。它在产生代码运行完成之前运行完成。没有第二个渲染事件。
解决方案是让ButtonClick返回一个Task:
async Task ButtonClick()
{
// set some message saying processing
// Call Task.Wait to simulate some yielding async work
await Task.Wait(1000);
// set some message saying complete
StateHasChanged();
}
现在事件处理程序task有一些东西要等待。
几乎所有UI事件都使用相同的模式。您还可以看到它在OnInitializedAsync和OnParametersSetAsync中使用。
那么最佳实践是什么?何时在事件处理程序中使用void和Task?
通常,不要将async关键字与void关键字混在一起。如果有疑问,请通过Task.
总结从这篇文章中获取的关键信息是:
- RenderFragment是一个delegate——它是一个使用RenderTreeBuilder来构建html标记的代码块。
- StateHasChanged不渲染组件或执行RenderFragment.。它将一个RenderFragment推送到Renderer的队列中。
- UI事件处理程序需要让出给Renderer线程时间来运行它的渲染队列。
- UI事件处理程序不是一劳永逸的。
- 不要像这样声明事件处理程序:async void UiEvent()。如果是async,那么就是async Task UiEvent()。
演示页面
这是一个独立的页面,展示了上面讨论的一些问题和解决方案。长时间运行的任务是实数运算方法(寻找质数),以演示真正的同步和异步长时间运行的操作。Task.Yield每次找到质数时,异步版本都会调用以产生执行控制。您可以使用此页面来测试各种场景。
@page "/"
@using System.Diagnostics;
@using Microsoft.AspNetCore.Components.Rendering;
UI Demo
@MyDiv
@MyOtherDiv
Primes to Calculate:
Click Event
Click Async Void Event
Click Async Task Event
Reset
@code{
bool workingstate;
string buttoncolour => workingstate ? "btn-danger" : "btn-success";
string MyDivColour => workingstate ? "bg-warning" : "bg-primary";
string myOtherDivColour => workingstate ? "bg-danger" : "bg-dark";
long tasklength = 0;
long primesToCalculate = 10;
string message = "Waiting for some action!";
private async Task Reset()
{
message = "Waiting for some action!";
workingstate = false;
}
private async Task ClickedAsync()
{
workingstate = true;
message = "Processing";
await LongYieldingTaskAsync();
message = $"Complete : {DateTime.Now.ToLongTimeString()}";
workingstate = false;
}
private void Clicked1()
{
workingstate = true;
message = "Processing";
LongTaskAsync();
message = $"Complete : {DateTime.Now.ToLongTimeString()}";
workingstate = false;
}
private async void Clicked2()
{
workingstate = true;
message = "Processing";
await Task.Yield();
await LongTaskAsync();
message = $"Complete : {DateTime.Now.ToLongTimeString()}";
workingstate = false;
}
private RenderFragment MyDiv => (RenderTreeBuilder builder) =>
{
builder.AddMarkupContent(0, $"
{message}");
};
private RenderFragment MyOtherDiv => (builder) =>
{
builder.OpenElement(0, "div");
builder.AddAttribute(1, "class", $"text-white {myOtherDivColour} m-2 p-2");
builder.AddMarkupContent(0, message);
builder.CloseElement();
};
public Task LongTaskAsync()
{
var watch = new Stopwatch();
var num = primesToCalculate * 1;
watch.Start();
var counter = 0;
for (long x = 0; x
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?


微信扫码登录