c# - 剃刀页面内的 Blazor 页面和组件代码?(Blazor 图案)
问题描述
在 razor 页面中处理 Blazor 时是否有一些特定的模式来编写代码?我看到的所有关于 Blazor 的示例都将 HTML/Razor/CSS 和 C# 代码放在同一个文件中,例如:
<div class="text">@sometext</div>
<style> .text{ color:black; } </style>
@code{
sometext = "text.."
}
是否有一些模式可以将 C# 和 HTML 分开,就像在 MVC 中一样?我将大部分逻辑从@code{}
页面的单独服务类中移出。
我很想听听不同的方法,最佳实践是什么?
解决方案
实际问题提出了两个不同的问题,需要很长的答案。
有几种模式可以使用,例如 MVVM、MVU。Blazor WebAssembly 大致是 React#,因此使用 React 中使用的相同组件逻辑有很大帮助。
两个框架都构建 SPA,因此关注点相同,因此解决方案也相似。就像所有的 SPA 框架一样,MVC 在大约十年前被 MVVM 取代,例如 Backbone。多个组件之间的状态管理作为一个单独的问题出现,因此 SPA 具有不同的模式和机制来管理状态(想想 React 的 Redux 和替代方案)。
甚至存储也很重要。ASP.NET Core Blazor 状态管理在多个地方描述存储:
- 服务器数据库
- 查询参数
- 浏览器本地存储
- 内存缓存
可以使用多种模式 - 类似 React 的组件,其主状态保存在“父”级别的 ViewModel 中,无论它在哪里。
在 Blazor 的数据绑定中,如 React,数据和状态通过组件Parameter
属性从父级向下流动。对数据的更改通过EventCallback
s 向上流动。这使得 MVVM 和 MVU 等模式的实现变得容易。
组件级设计
在 React(和 Blazor)中,数据和状态从父级向下流动。数据的变化向上流动。Blazor 的数据绑定旨在促进这一点,尽管它不限制你:
- 实际状态由父母持有,无论父母想要什么形式。它可能是多个属性、模型/DTO 类或跟随的 ViewModel。数据通过组件
Parameter
属性传递给嵌套组件。 - 例如响应事件的变化通过
EventCallback
s 发出信号。嵌套属性本身并不存储更改的数据。
在从文档复制的此示例中,ChildBind
组件永远不会更改Year
属性本身。它告诉其父级更改的年份,并将其留给父级(和数据绑定)告诉它要重绘什么:
Shared/ChildBind.razor
<div class="card bg-light mt-3" style="width:18rem ">
<div class="card-body">
<h3 class="card-title">ChildBind Component</h3>
<p class="card-text">
Child <code>Year</code>: @Year
</p>
<button @onclick="UpdateYearFromChild">Update Year from Child</button>
</div>
</div>
@code {
private Random r = new();
[Parameter]
public int Year { get; set; }
[Parameter]
public EventCallback<int> YearChanged { get; set; }
private async Task UpdateYearFromChild()
{
await YearChanged.InvokeAsync(r.Next(1950, 2021));
}
}
可以通过一些工作或使用级联值和参数来绑定超过 2 个组件。
应用程序架构 - 概述
在 React 和 Blazor 中设计复杂 SPA 的明显方法是组合组件。这两个框架都是按照这种方式构建的。不太明显的是如何在这些组件之间移动数据/状态和事件,谁将存储数据,谁将更改它,更改最终如何修改 UI 等等。
而且由于我们谈论 SPA,即使是“位置”也没有明确的答案,状态保存在查询参数、浏览器存储或远程服务中。
选择架构取决于需要解决的问题。没有best practice
。模式的定义是
在上下文中解决问题
这意味着在一种情况下是好的解决方案在另一种情况下不会是好的解决方案。
应用架构 0 - 组件组成
这几乎是使用 Blazor 的“默认”方式。使用包含组件的多个页面。数据下降,变化上升,试图横盘整理基本上是不可能的,因此开发人员必须顺其自然。
如果页面或组件变得太大,很容易将其提取到具有自己Parameter
的 s 的单独组件中。
在某些情况下,组件组合就足够了,尤其是当不需要管理很多复杂的状态或者组件之间没有那么多依赖项时。这些组件可以在不增加复杂性的情况下调用自己的服务器端 API。不同的人可以在一定程度上轻松地独立处理自己的组件。
许多快速项目在变得足够大以需要更复杂的架构之前都是这样开始的。当这种情况发生时,使用组件可以更容易地采用另一种架构。
但是,在例如 ERP 或 CRM 应用程序中,即使是单个客户也需要管理大量复杂的数据。同一页面可能会以摘要或详细视图的形式显示来自多个系统的多个选项卡。单个更改可能会影响同一页面上多个组件的显示。
不要介意这样的应用程序通常需要组合来自不同应用程序的 UI 元素,并且仍然作为单个应用程序运行。15 年前,这些被称为Composite Applications
or Mashups
。现在人们谈论Micro-Frontends
应用架构1-MVVM
这种模式在 SPA 和 WPF 中被大量使用。在这个设计ViewModel
中,每个都使用一个单独View
的,实际状态存储在一个Model
. Model
可能是隐藏数据库或服务调用。实现自己的View/ViewModel
s 的多个组件可以使用相同的 s Model
,例如在每个页面上以不同的方式显示当前用户数据。
有很多关于 MVVM 的文章。我从Blazor For State Management – A Complete Guide 中的 MVVM Pattern 复制了图像。
View 和 ViewModel 之间的通信使用 Blazor 数据绑定。类ViewModel
本身需要实现INotifyPropertyChanged
,就像在 WPF 中一样,因此对其属性的更改可以触发视图更新。
应用架构 - MVU
这种模式起源于 Elm 语言,因此也被称为Elm Architecture
. 它越来越受欢迎,.NET MAUI 可以在 MVU 的一个变体上运行。本文介绍了它在 F# 中的工作原理。
高级图像显示了它的主要特征 - 流程只是单向的。该Update
部件生成用于更新Model
. 听起来很像Redux,不是吗?
下图来自Comet,这是 .NET MAUI 的一种 MVU 模式,它更清楚地说明了这些概念如何映射到 Blazor 概念(如数据绑定):
在这种情况下,组件Parameter
s 提供单向绑定,但EventCallback
s 需要生成更新Model
而不是通知父组件。
在Redux
中,Command
s 和Update
s 都是顶层概念,预先定义,类似于消息,因此应用程序状态和组件可以相互独立地更改。
将代码与标记分离
Blazor 组件概述页面中的部分类支持部分显示您可以将 C# 代码放在代码隐藏文件中,该文件包含名称与您的组件相同的部分类。在编译期间,来自所有分部类的代码被合并到一个单独的类中并编译为一个类。
如果您有一个名为的组件,请从文档中借用Pages/Counter.razor
:
page "/counter"
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
您可以将其拆分为一个.razor
和一个.cs
文件:
CounterPartialClass.razor
@page "/counter-partial-class"
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
CounterPartialClass.razor.cs
namespace BlazorSample.Pages
{
public partial class CounterPartialClass
{
private int currentCount = 0;
void IncrementCount()
{
currentCount++;
}
}
}
推荐阅读
- sql - 在第一次出现符号之前从字符串中提取文本
- flutter - 如何在 Flutter 的小部件树中将新的 MaterialPageRoute 作为子项打开
- angular - Angular 9:一种形式的多个 ngb-typeahead?
- php - 添加两个多维 PHP 关联数组
- android - Android Room - 定义 Room 表名、ColumnInfo 等时无法解析方法/符号
- angular - Angular 9:无法通过依赖注入创建该类,因为它没有 Angular 装饰器
- spring - 我可以将 CircleCI 中的环境变量传递给 Spring Boot 中的代码吗?
- snowflake-cloud-data-platform - ServiceNow 连接到雪花
- javascript - 如何从对象数组创建单个数组
- r - 我怎样才能在 R 中使用 3 种不同颜色随机着色图形点,仅使用 R 基本