Blazor UI事件和渲染

目录

介绍

代码

组件渲染

Blazor UI事件

Blazor UI事件模式

总结

附录


介绍

对于刚接触Blazor的程序员来说,最常见的问题之一是UI事件和相关的渲染过程。此类问题每天都会发布在StackOverflow等网站上。希望这篇文章能消除一些疑惑!

代码

本文没有代码库。在本文的附录中有一个单页演示Razor文件,您可以使用它进行测试。

渲染片段

什么是RenderFragment

对于许多人来说,它看起来像这样:

<div>
 Hello World
</div>

string——Razor中的标记块。

深入到DotNetCore代码库,你会发现:

public delegate void RenderFragment(RenderTreeBuilder builder);

如果您不完全理解委托,请将其视为一种模式。任何符合模式的函数都可以作为RenderFragment

该模式规定您的方法必须:

  1. 有一个,而且只有一个,类型为RenderTreeBuilder的参数。
  2. 返回一个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组件:

<div>
 Hello World
</div>

被编译成:

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, "<div>\r\n Hello World\r\n</div>");
        }
    }
}

组件渲染

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);
    };

首先,它检查渲染是否已经排队——_hasPendingQueuedRenderfalse。如果不是,则设置_hasPendingQueuedRendertrue并调用_renderHandle.Render传递它_renderFragment(组件的渲染片段)。就是这样。

_hasPendingQueuedRender实际运行渲染片段时设置为false。对于好奇的人,_renderHandle在被附加到RenderTreeRenderer调用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事件都使用相同的模式。您还可以看到它在OnInitializedAsyncOnParametersSetAsync中使用。

那么最佳实践是什么?何时在事件处理程序中使用voidTask

通常,不要将async关键字与void关键字混在一起。如果有疑问,请通过Task.

总结

从这篇文章中获取的关键信息是:

  1. RenderFragment是一个delegate——它是一个使用RenderTreeBuilder来构建html标记的代码块。
  2. StateHasChanged不渲染组件或执行RenderFragment.。它将一个RenderFragment推送到Renderer的队列中。
  3. UI事件处理程序需要让出给Renderer线程时间来运行它的渲染队列。
  4. UI事件处理程序不是一劳永逸的。
  5. 不要像这样声明事件处理程序:async void UiEvent()。如果是async,那么就是async Task UiEvent()

附录

演示页面

这是一个独立的页面,展示了上面讨论的一些问题和解决方案。长时间运行的任务是实数运算方法(寻找质数),以演示真正的同步和异步长时间运行的操作。Task.Yield每次找到质数时,异步版本都会调用以产生执行控制。您可以使用此页面来测试各种场景。

@page "/"
@using System.Diagnostics;
@using Microsoft.AspNetCore.Components.Rendering;

<h1>UI Demo</h1>

@MyDiv

@MyOtherDiv

<div class="container">
    <div class="row">
        <div class="col-4">
            <span class="col-form-label">Primes to Calculate: </span>
            <input class="form-control" @bind-value="this.primesToCalculate" />
        </div>
        <div class="col-8">
            <button class="btn @buttoncolour" @onclick="Clicked1">Click Event</button>
            <button class="btn @buttoncolour" @onclick="Clicked2">
             Click Async Void Event</button>
            <button class="btn @buttoncolour ms-2" @onclick="ClickedAsync">
             Click Async Task Event</button>
            <button class="btn @buttoncolour" @onclick="Reset">Reset</button>
        </div>
    </div>
</div>
@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, $"<div class='text-white {MyDivColour} m-2 p-2'>
        {message}</div>");
    };

    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 <= num; x++)
        {
            for (long i = 0; i <= (10000); i++)
            {
                bool isPrime = true;
                for (long j = 2; j < i; j++)
                {
                    if (i % j == 0)
                    {
                        isPrime = false;
                        break;
                    }
                }
                if (isPrime)
                {
                    counter++;
                }
            }
        }
        watch.Stop();
        tasklength = watch.ElapsedMilliseconds;
        return Task.CompletedTask;
    }

    public async Task LongYieldingTaskAsync()
    {
        var watch = new Stopwatch();

        var num = primesToCalculate * 1;
        watch.Start();
        var counter = 0;
        for (long x = 0; x <= num; x++)
        {
            for (long i = 0; i <= (10000); i++)
            {
                bool isPrime = true;
                for (long j = 2; j < i; j++)
                {
                    if (i % j == 0)
                    {
                        isPrime = false;
                        break;
                    }
                }
                if (isPrime)
                {
                    counter++;
                    await Task.Yield();
                }
            }
        }
        watch.Stop();
        tasklength = watch.ElapsedMilliseconds;
    }
}

https://www.codeproject.com/Articles/5310624/Blazor-UI-Events-and-Rendering

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值