65.9K
CodeProject 正在变化。 阅读更多。
Home

Blazor WebAssembly vs Angular:客户端之战

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2020年8月9日

CPOL

10分钟阅读

viewsIcon

12639

你应该使用哪种 SPA 技术:Blazor WebAssembly 还是更成熟的 Angular,为什么?

快速思考:你正在启动一个新项目,应该使用哪种单页应用程序 (SPA) 技术:你听说过很多的新潮 Blazor WebAssembly,还是更成熟的 Angular,为什么?

这是我构建 typershark.io 时试图回答的主要问题,这是一个简单的基于网络的单人或多人合作游戏,我构建它的目的是为了鼓励我女儿练习打字,同时学习 Blazor。

我目前肯定不是 Blazor 专家,但我已经投入了 40 多个小时来构建这个游戏,并且在过去两年里一直在 Angular 2+ 应用程序中专业工作,我觉得可以比较这些技术并分享我的感受。此外,作为一名顾问,我在过去的二十年里参与了 38 个独特的项目,对启动新项目时面临的考虑因素,以及做出糟糕的架构决策后感到的遗憾并不陌生。

我将在十个类别中对它们进行比较,我认为它们的差异很有趣,并且可能会影响项目的技术选择

  1. 视图类型化
  2. 组件级 CSS
  3. 验证
  4. 工具
  5. 成熟度
  6. 语言
  7. 调试
  8. 可测试性
  9. 互操作
  10. 代码共享

首先,我将快速概述 Blazor WebAssembly 技术,以防您错过它。我假设您对 Angular 有最基本的了解。

Blazor WebAssembly,它是什么?

于 2017 年宣布,并于几个月前,即 2020 年 5 月 19 日正式发布,Blazor WebAssembly 是一种允许开发人员使用 C# 和 Razor(HTML 和 C# 的愉快混合体)编写客户端代码并将其编译为 WebAssembly 的技术。

WebAssembly 令人兴奋,因为它比 JavaScript 更快、更紧凑,并且功能更多。如果这听起来像我在吹嘘,请阅读 Jeremy Likness 的这篇出色的 Blazor 文章,下面是摘录

作为一种字节码格式,无需解析脚本和预编译以进行优化。代码可以直接转换为本机指令。与 asm.js 相比,加载和开始执行代码的启动时间快了几个数量级。

WebAssembly 之上,Blazor 添加了数据绑定、组件、JavaScript 互操作、依赖注入,以及运行您可以从 NuGet 获取的任何 .NET Standard 代码的能力——所有这些都在浏览器中原生运行。如果这听起来令人兴奋,那确实如此。但是它准备好用于生产了吗?

顺便说一句,如果您想了解更多关于 Blazor WebAssembly 的功能,请查看 Code Hour 的第 30 集。

服务器端还是客户端?

在本文中,我不会探讨 Blazor 更成熟的服务器端执行模型。在该模式下,Blazor 不会将 C# 编译为 WebAssembly,而是将 C# 运行在服务器端,并通过 SignalR 传递 HTML,类似于 WebForms 中的 AJAX UpdatePanel(还记得那些吗?)。服务器端 Blazor 支持较旧的浏览器(IE 11+),但它没有离线支持,并且服务器需要为每个客户端维护状态,这限制了可伸缩性。

顺便说一句,我用服务器端 Blazor 编写了 typershark.io 的第一个版本,并将其进行了转换。虽然服务器端和 WebAssembly Razors 看起来几乎相同,但它们的架构从根本上非常不同,因此转换并非易事。如果您正在启动一个新项目,请提前选择您的执行模型,不要计划切换。

Components

在进入这些类别之前,我将介绍一些代码来为讨论奠定基础。这些代码将以组件的形式出现。在 Angular 和 Blazor 中,组件都实现了信息隐藏,从而提高了可维护性。

这是 GetPlayerName 组件的 Blazor 代码,它可以接受默认玩家名称(可能从本地存储中获取),提示用户输入名称,并在提交后返回玩家提供的名称

<EditForm Model="TempPlayer" OnValidSubmit="SetName">
    <DataAnnotationsValidator />
    <ValidationSummary />
    ....
    <InputText type="text" @bind-Value="TempPlayer.Name" />
    ...
</EditForm>

code {
    [Parameter]
    public EventCallback OnSetName { get; set; }

    [Parameter]
    public string InitialName { get; set; }

    

    private async Task SetName()
    {
        await OnSetName.InvokeAsync(TempPlayer.Name);
    }
}

然后可以像这样从父组件中使用

<PlayerNameComponent InitialName="@MyInitialName" OnSetName="@OnSetName" />

@code
{
    private string PlayerName

    public void OnSetName(string playerName) 
    {
        // do something
    }
}

1. 视图类型化:Blazor++

查看 PlayerNameComponent 第 1 行的 EditForm 元素,及其 Model 属性。这是一个 Blazor 提供的组件,它在 HTML 中转换为 Form,但好处是如果存在类型不匹配,可以在视图中获得强类型错误。这使得重构更安全、更容易,并提供了出色的 IntelliSense。它真的很棒!

相比之下,一个 Angular PlayerName 组件感觉非常相似

@Component({
  template: `
  <form type="submit" (ngSubmit)="addPlayer()">
    <input type="text" name="PlayerName" [(ngModel)]="playerName">
    <button type="submit">Save Player</button>
  </form>
  `,
  styleUrls: ['./add-player.component.scss'],
})
  public playerName: string;

  @Input()
  public defaultPlayerName: string;

  @Output()
  public onAddPlayer: EventEmitter<string> = new EventEmitter<string>();

  public addPlayer() {
    this.onAddPlayer.emit(this.playerName);
  }

  public ngOnInit(): void {
    this.playerName = this.defaultPlayerName;
  }
}

下面是如何在 Angular 中使用该组件

@Component({
  template: `
        <app-add-player defaultPlayerName="Sally" (onAddPlayer)="myOnAddPlayer($event)">
        </app-add-player>
        Your name is {{ playerName }}
    `,
})
export class HomeComponent extends AppComponentBase {
  public playerName = "initial value";

  public myOnAddPlayer(playerName: string) {
    this.playerName = playerName;
  }
}

模板代码看起来很美观(确实如此,我真的很喜欢 Angular 的视图和数据绑定语法),但在运行时之前,几乎没有语法验证,当然也没有类型检查。并且 IntelliSense 很差,即使视图位于单独的 *.html* 文件中(为了可读性,我在这里将所有代码示例的视图和组件结合在一起)。

您可以在 Angular CLI 中通过 ng build --prod --aot 捕获一些问题(在 Angular 9 中通过严格模板检查捕获更多),但完整的编译在大型应用程序上可能需要几分钟才能运行,它不是开发过程的常规部分,并且仍然缺少类型检查。这使得重构更危险,并且 IntelliSense 几乎无用。Blazor 绝对赢得了这一类别。

2. 组件级 CSS:Angular++

Blazor 真正失败的地方隐藏在上述 Angular AddPlayerComponent 代码示例的第 8 行:styleUrls: ['./add-player.component.scss'],。Angular 捆绑*组件级* CSS、LESS 或 SCSS 样式的功能对于任何规模的 SPA 应用程序都至关重要。目前我根本无法回到没有它的框架。幸运的是,它可能即将出现:查看 Blazor 关于 CSS 隔离的问题(即将推出)。一旦解决了这个问题,Blazor 将为我消除一个巨大的负面因素。

3. 验证:Angular++

从验证的角度再次查看 Blazor 的 AddPlayerComponent。我保留了 DataAnnotationsValidatorValidationSummary 以展示 Blazor 中验证的极其简单。它只是获取并使用 C# 数据注解。我喜欢这一点。不幸的是,它感觉不如 Angular 验证那么健壮。

Angular 可以跟踪每个字段的脏状态或无效状态,并将其汇总到表单甚至子表单级别(想象您有一个包含多个部分的表单)。响应式表单模型(与我上面展示的模板驱动表单方法相对)提供的灵活性带来了巨大的优势,我无法在 Blazor 中找到任何可比较的东西(尽管和 Blazor 的所有东西一样,我对此很陌生,如果我错过了什么,请在评论中写下)。因此,验证是 Angular 的胜利,但 Blazor 在其简单性方面也表现出色。

4. 工具:Angular++

你是不是以为我会因为 Blazor 在表单中具有强类型和 IntelliSense 而判它赢?没那么快。还有大约三个其他缺点掩盖了这一胜利。

首先,在 Angular 中添加新组件、指令或管道,使用 Angular CLI 很容易。只需 ng generate component MyNewComponent,您就拥有了一个组件类、视图模板、CSS/LESS/SCSS 文件和一个测试文件,并且 Angular 会为您注册它。另一方面,Blazor 没有提供这些。如果您想要独立的视图和组件文件,您必须知道添加正确名称的“代码隐藏”并将其设置为局部。糟糕。

其次,虽然 IntelliSense 在表单中运行良好,但我发现它经常无法找到内置组件,例如前面提到的 ValidationSummary,我必须了解并复制粘贴。此外,视图错误经常无法显示为编译器错误,而只在运行时在 DevTools 中显示,或者有时在 IDE 中随机出现的某个隐藏生成的视图文件中显示。糟糕。

最后,Angular 的热重载使其开发内循环更快。我在 Blazor 中能达到的最接近的是按 Control F5(不调试运行),然后我可以对 *.razor* 或 *.css* 文件进行更改(但不能是代码)并刷新,我很快就能看到非 C# 的更改。

总的来说,工具是 Angular 的一个巨大胜利,但请继续关注,我打赌 Blazor 在下一个版本中会表现出色。

5. 成熟度:Angular++

平台的成熟度很重要,因为遇到边缘情况或探索 StackOverflow 上没有答案的新场景可能会大大延迟您的项目。我只在构建 TyperShark 时遇到了几个这样的问题,实际上我对现有的内容印象深刻。Blazor 显然有一个热情的粉丝群。尽管如此,如果您正在构建生产应用程序并且时间紧迫,那么 Angular 仍然赢得了这一类别,请坚持使用成熟的平台。

6. 语言:Razor++

⚠️ 有争议的观点预警

https://xkcd.com/2051/

根据 StackOverflow 2019 年的开发者调查,C# 是第 10 个最受欢迎的语言,而 TypeScript 是第 3 个。对此有一个简单的解释。那些开发者都是傻瓜,我将把这一类别授予 Razor,不再争论。

好吧,我撒谎了,再多说一句。在 Angular 中从后端检索数据通常涉及 RxJS 订阅。我发现在 99% 的场景中 RxJS 都过于复杂和晦涩。考虑一下这个 RxJS 代码

this.userService
    .findAll()
    .finally(() => {
        finishedCallback();
    })
    .subscribe(
        (users: UserDtoPagedResultDto) => {
             this.users = users.items;
             this.showPaging(users);
         },
         err => console.log('HTTP Error', err),
     );

与 Razor 中的替代方案对比

try {
    var users = await _userService.FindAll()
    _users = users.Items;
    ShowPaging(users);
} catch (Exception ex) {
    Logger.LogInformation("HTTP Error", err);
} finally {
    FinishedCallback?.Invoke();
}

如果第一个代码片段没有升高你的血压,而第二个代码片段没有降低你的血压,你可能需要去看医生。

7. 调试:Angular++

目前,Blazor WebAssembly 无法在未处理的异常处中断或在应用程序启动期间命中断点。这让我非常沮丧。这意味着对于大多数错误,我必须将堆栈跟踪从 Chrome Dev Tools 复制并粘贴到 Resharper 的 Browse Stack Trace 工具中(有史以来最好的隐藏功能)。我知道这个问题很快就会得到解决,但现在非常令人沮丧。好消息是,一旦这个问题得到解决,我怀疑我会更喜欢 Visual Studio 的调试体验。

8. 可测试性:平局

Blazor 具有极高的可测试性。Blazor 提供的所有内容都是用于模拟的接口,并且依赖注入是一等公民。他们显然大量借鉴了 Angular 的优势。如果 Microsoft 过去对可测试性缺乏尊重曾让你望而却步:请重新考虑,这不再是一个问题。

9. 互操作:Angular++

显然,JavaScript 互操作(最常见的场景)在 Angular 中不是问题,所以 Angular 胜出。也就是说,从 JavaScript 调用 .NET 代码(例如,TyperShark 的 onkeypress 事件处理程序)或使用 IJSRuntime 从 .NET 调用 JavaScript 都非常容易。此外,与从 npm 下载的包集成是可行的,尽管如果需要的话,您可能做错了。

反之,互操作是双向的。如果您有一个 JavaScript 中不存在但您需要在客户端使用的 NuGet 库,那么 Angular 就无能为力了,Blazor 取得了胜利!考虑到庞大的 JavaScript 生态系统,这种场景似乎不太可能发生,所以我不得不将胜利归于 Angular。

10. 代码共享:Blazor++

我把最好的留到最后。typershark.io 可以运行在单人模式或多人模式下。它有一个核心引擎,可以生成鲨鱼、跟踪活跃的鲨鱼、计算鲨鱼何时超时,并跟踪哪些用户(假设是多人模式)输入了鲨鱼的哪些元素。它会发出诸如 SharkAddedGameOverGameChanged 等通知。它不是超级复杂,也不是超级简单。它的美妙之处在于:我能够让它在单人模式下在客户端运行以实现离线支持,或者在多人模式下运行时完全在服务器端运行,并带有 SignalR 回调事件。

我再重复一遍:完全相同的 C# 代码可以根据运行时条件在浏览器*或*服务器中运行。

根据您正在构建的内容,此功能可能*非常庞大*,而且我从未需要考虑我的代码在哪里运行这一事实令人惊叹。

诚然,您可能可以使用 node.js 和 Angular 实现相同的功能,但如果您正在使用 ASP.NET Core 堆栈并且也喜欢 C#,那么这绝对是一个很棒的功能。

结论

如果游戏听起来很有趣,请在 typershark.io 上查看(放低期望)。如果代码听起来很有趣,请在 github 上查看(我接受拉取请求)。

更重要的是,您是否应该用 Blazor WebAssembly 启动您的下一个项目?这取决于。如果您工期紧,需要大量的 JavaScript 互操作,或者需要成熟度和稳定性,那么选择 Angular、Vue 或 React。但是,如果您需要在客户端和服务器端共享 C# 代码,有一个想要互操作的 NuGet 包,或者像我一样真正喜欢 C#,那么请选择 Blazor。无论如何,Blazor 今天既明亮又闪耀,使用起来很有趣,并且它有着更光明的未来。我会密切关注它,如果您已经读到这里,您也绝对应该这样做。

© . All rights reserved.