Blazor WebAssembly vs Angular:客户端之战





5.00/5 (4投票s)
你应该使用哪种 SPA 技术:Blazor WebAssembly 还是更成熟的 Angular,为什么?
快速思考:你正在启动一个新项目,应该使用哪种单页应用程序 (SPA) 技术:你听说过很多的新潮 Blazor WebAssembly,还是更成熟的 Angular,为什么?
这是我构建 typershark.io 时试图回答的主要问题,这是一个简单的基于网络的单人或多人合作游戏,我构建它的目的是为了鼓励我女儿练习打字,同时学习 Blazor。
我目前肯定不是 Blazor 专家,但我已经投入了 40 多个小时来构建这个游戏,并且在过去两年里一直在 Angular 2+ 应用程序中专业工作,我觉得可以比较这些技术并分享我的感受。此外,作为一名顾问,我在过去的二十年里参与了 38 个独特的项目,对启动新项目时面临的考虑因素,以及做出糟糕的架构决策后感到的遗憾并不陌生。
我将在十个类别中对它们进行比较,我认为它们的差异很有趣,并且可能会影响项目的技术选择
- 视图类型化
- 组件级 CSS
- 验证
- 工具
- 成熟度
- 语言
- 调试
- 可测试性
- 互操作
- 代码共享
首先,我将快速概述 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
。我保留了 DataAnnotationsValidator
和 ValidationSummary
以展示 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++
⚠️ 有争议的观点预警
根据 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 可以运行在单人模式或多人模式下。它有一个核心引擎,可以生成鲨鱼、跟踪活跃的鲨鱼、计算鲨鱼何时超时,并跟踪哪些用户(假设是多人模式)输入了鲨鱼的哪些元素。它会发出诸如 SharkAdded
、GameOver
或 GameChanged
等通知。它不是超级复杂,也不是超级简单。它的美妙之处在于:我能够让它在单人模式下在客户端运行以实现离线支持,或者在多人模式下运行时完全在服务器端运行,并带有 SignalR 回调事件。
我再重复一遍:完全相同的 C# 代码可以根据运行时条件在浏览器*或*服务器中运行。
根据您正在构建的内容,此功能可能*非常庞大*,而且我从未需要考虑我的代码在哪里运行这一事实令人惊叹。
诚然,您可能可以使用 node.js 和 Angular 实现相同的功能,但如果您正在使用 ASP.NET Core 堆栈并且也喜欢 C#,那么这绝对是一个很棒的功能。
结论
如果游戏听起来很有趣,请在 typershark.io 上查看(放低期望)。如果代码听起来很有趣,请在 github 上查看(我接受拉取请求)。
更重要的是,您是否应该用 Blazor WebAssembly 启动您的下一个项目?这取决于。如果您工期紧,需要大量的 JavaScript 互操作,或者需要成熟度和稳定性,那么选择 Angular、Vue 或 React。但是,如果您需要在客户端和服务器端共享 C# 代码,有一个想要互操作的 NuGet 包,或者像我一样真正喜欢 C#,那么请选择 Blazor。无论如何,Blazor 今天既明亮又闪耀,使用起来很有趣,并且它有着更光明的未来。我会密切关注它,如果您已经读到这里,您也绝对应该这样做。