从之前的.NET5到现在的.NET7,Blazor还是获得了长足的进步,需要重新审视下了。

  • ASP.NET Core Blazor
  • ASP.NET Core Blazor supported platforms
    • 主流的都支持,hybrid的在最新的Edge WebView2、Android的Chrome以及iOS和macOS的Safari上测过
  • Tooling for ASP.NET Core Blazor
    • 讲述怎么使用VS、VS Code以及donet命令行来搭建Blazor工程
  • ASP.NET Core Blazor tutorials
  • ASP.NET Core Blazor hosting models
    • 又复述了一遍Blazor Server、Blazor WebAssembly、Blazor Hybrid三种侯宿模式
    • Which Blazor hosting model should I choose?
      • 以表格形式列举了各种侯宿模式的优缺点
  • ASP.NET Core Blazor Hybrid
    • Overview
      • 开头
        • 采用本地WebView,服务端也在本地,采用本地互操作通道
      • Blazor Hybrid apps with .NET MAUI
      • Blazor Hybrid apps with WPF and Windows Forms
    • ASP.NET Core Blazor Hybrid tutorials
      • 针对MAUI、WinForms和WPF的起步教程
    • ASP.NET Core Blazor Hybrid routing and navigation
      • 开头
        • 默认的URI请求的路由行为:
          • 区分内部链接和外部链接
          • 内部链接在BlazorWebView中打开
          • 外部链接在系统指定的浏览器中打开
          • 如果内部链接请求了一个文件,但是不在静态内容中
            • WPF和WinForms,返回的是侯宿页面的内容
            • MAUI,返回404
        • UrlLoading可以用于改变target="blank"以外的链接打开形式,可改成OpenExternallyOpenInWebView以及CancelLoad
      • Namespace
        • 需要用到Microsoft.AspNetCore.Components.WebView;
      • Internal navigation(略)
      • External navigation(略,内容有所重复)
    • ASP.NET Core Blazor Hybrid static files
    • Use browser developer tools with ASP.NET Core Blazor Hybrid
      • Browser developer tools with .NET MAUI Blazor
        • 通过配置AddBlazorWebViewDeveloperTools来支持开发工具
        • 在BlazorWebView中使用Ctrl+Shift+I开启开发工具
    • Reuse Razor components in ASP.NET Core Blazor Hybrid
      • 开头部分
        • 不同侯宿平台具有不同的能力
          • WASM支持同步JS互操作,Server和Hybrid只支持异步JS互操作
          • Server中的组件能够访问只存在于服务端的服务,比如Entity Framework数据库上下文
          • BlazorWebView 中的组件可以直接访问原生桌面于移动设备的特性
      • Design principles
        • 下列是设计能跨平台工作的组件的一些原则
          • 将UI代码放置在Razor标类料库(RCL)
          • 特定功能的实现不放在RCL,RCL应只存放抽象层(接口和基类)
          • 只在特定侯宿平台加入平台特定功能
          • 通常,为组件中的HTML使用CSS样式,并且在CSS样式中表达不同平台的差异
          • 如果UI某部需要针对特定平台有额外的处理逻辑,则在RCL中使用https://learn.microsoft.com/en-us/aspnet/core/blazor/components/dynamiccomponent?view=aspnetcore-7.0封装之。也可以使用RenderFragment实例。
      • Project code organization
    • Share assets across web and native clients using a Razor class library (RCL)
      • 文首
        • 如何在Server和MAUI应用中共享静态物资
      • Sample app
      • Share web UI Razor components, code, and static assets
        • 重复复述了一些其他文章中的内容
        • index.html通常不共享
        • 根部的Razor组件,比如App.razorMain.razor倒是可以共享,不过通常这些含有跟侯宿方式相关的代码
      • Provide code and services independent of hosting model
        • 侯宿方式相关的代码,应抽象成接口,后通过服务的方式注入
        • 文中举了weather forecast的例子
      • Security and Identity
        • (暂略)
  • ASP.NET Core Blazor project structure
    • Blazor Server
      • 相关的模板blazorserver, blazorserver-empty(没有bootstrap CSS和示例代码)
      • Project structure
        • Data目录
        • Pages目录,盛放.razor文件
        • Properties/launchSettings.json,开发环境相关的设置
        • Shared目录
          • MainLayout.razor,应用的布局管理
          • MainLayout.razor.css
          • NavMenu ,侧边栏导航
          • NavMenu.razor.css
          • SurveyPrompt.razor,survey组件
        • wwwroot,Web根目录
        • _Imports.razor,公共Razor指示
        • App.razor,应用根组件,路由在此设置
        • appsettings.json,提供https://learn.microsoft.com/en-us/aspnet/core/blazor/fundamentals/configuration?view=aspnetcore-7.0
        • Program.cs,应用的入口点
    • Blazor WebAssembly (略)
    • Location of <head> content
      • Server应用在Pages/_Host.cshtml,WASM应用中在wwwroot/index.html
    • Dual Blazor Server/Blazor WebAssembly app
      • 推荐使用RCL来共享代码和逻辑
  • Fundamentals
    • Routing and navigation
      • Route to components from multiple assemblies
        • 在Router组件上使用AdditionalAssemblies,示例
          <Router
            AppAssembly="@typeof(App).Assembly"
            AdditionalAssemblies="new[] { typeof(Component1).Assembly }">
            @* ... Router component elements ... *@
          </Router>
          
    • Configuration
      • 本文只适用于Blazor WebAssembly
    • Dependency injection
      • DI 是这样的技术,组件依赖由框架来注入。于是乎,组件只要声明自己的依赖,具体注入的时机由框架给出
      • Default services

        • HttpClient(Scoped)
        • IJSRuntime(BW:Singleton,BS:Scoped)
        • NavigationManager(BW:Singleton,BS:Scoped)
        • 此外还有配置和录记等服务可以按需注入
      • Add services to a Blazor WebAssembly app

        • 具体的代码在Program.cs中
          • 注入的服务由Blazor的builder创建,创建之后可以调用方法来初始化,blazor提供配置信息(例如 host.Configuration["WeatherServiceUrl"])可以用于初始化。
      • Add services to a Blazor Server app

        • Microsoft.AspNetCore.Builder.WebApplicationBuilder的IServieCollection用于管理服务描述器,添加服务描述器:builder.Services.AddSingleton<IDataAccess, DataAccess>();
      • Register common services in a hosted Blazor WebAssembly solution

        • 可以在客户端提供可以用于客户端和服务端的服务注册功能
      • Service lifetime

        • Scoped,可清除的
        • Singleton,单现例的
        • Transient,一次性的
      • Request a service in a component

        • 使用@injectRazor指示来注入服务,需要两个参数
          • Type:服务的类型
          • Property:用于接收服务的辖属,不需要手动声明,会由编译器创建
        • 如果是在组件对应的标类中,使用[Inject]属性添加
        • 不要将待注入的服务设置成nullable,而是设置成:private IExampleService ExampleService { get; set; } = default!;
          • 文中列出了许多参考
      • Use DI in services

        • 如果一个服务需要注入另一个服务,需要在构造算函中指明所需服务
        • 注意事项具体见文中
          • 必须有一个参数其参量可以由DI满足,其他参数若无法满足,但有默认值也可以
          • 可用的构造算函必须是公开的
          • 如果存在多个可用的构造算函,则
      • Utility base component classes to manage a DI scope (略)

      • Use of an Entity Framework Core (EF Core) DbContext from DI(略)

      • Detect transient disposables in Blazor WebAssembly apps(略)

      • Detect transient disposables in Blazor Server apps(略)

    • Logging
      • Configuration
        • 默认只在Development情况下,录记LogLevel.Inofrmation级别信息
        • 工程文件配置<ImplicitUsings>enable</ImplicitUsings>的话,可以不使用using指明录记相关的标类
      • Log levels(过)
      • Razor component logging
        • ILogger<Counter>) LoggerFactory.CreateLogger<Counter>()
      • Logging in Blazor Server apps(略)
      • Logging in Blazor WebAssembly apps(略)
      • (剩余略)
  • Components
  • JavaScript intertop
    • Overview
      • Interaction with the Document Object Model (DOM)
        • 如果项件归Blazor管理,就不要用JS去变更DOM。否则会让Blazor和DOM的状态不匹配,出现未定义的行为
      • Asynchronous JavaScript calls
        • JS互操作默认是异步的,这是为了保证在不同侯宿模型中能有一致表现
          • 只有才WASM中,同步的JS互操作才是可能的
      • Object serialization
        • 项件序列化使用的是System.Text.Json,有以下规则
          • 类型必须有默认建构器,get/set访问器必须公开,域属不会被序列化
          • 全局的默认序列化无法定制
          • .NET成员名会被转成小写的JSON项件名
          • JSON逆序列化成JsonElement实例
        • JsonConverter用编口可用于自定义序列化。辖属序列化可以用[Jsonconverter]来自定义。
        • 文中引用了其他.NET文档
        • .NET 7.0计划支持System.DateOnly和System.TimeOnly
        • Blazor支持字节阵列,而不用将其转为Base64
      • JavaScript initializers
      • Location of JavaScript
        • 有几种办法
          • <head>加载,通常不推荐
          • <body>架子
          • 从与组件并存的外部js加载
          • 从外部js加载
          • 在Blazor启动时插入一个脚本
        • 不要在Razor组件中放置<script>,因为无法被Blazor动态更新
        • Load a script in <head> markup(过)
        • Load a script in <body> markup(过)
        • Load a script from an external JavaScript file (.js) collocated with a component(过)
        • Load a script from an external JavaScript file (.js)(过)
        • Inject a script after Blazor starts(过)
      • JavaScript isolation in JavaScript modules(略)
      • Cached JavaScript files
        • JS文件在开发环境下不设置缓存
        • 如果要清楚浏览器缓存
          • 在开发者工具中设置(文中有各大浏览器开发工具的链接)
          • 手动刷新页面
    • Call JS from .NET
      • 文首
        • 注入IJSRuntime 服务,即可调用JS
          • IJSRuntime.InvokeAsync
          • JSRuntimeExtensions.InvokeAsync
          • JSRuntimeExtensions.InvokeVoidAsync
        • 前面几个算函
          • JS作用域是全局,其他作用域需要显式指明,例如someScope.someFunction
          • 参数是JSON序列化后的Object[]
          • CancellationToken通知取消操作
          • TimeSpan知名操作时限
          • 返回值TValue必须是可JSON序列化的
          • InvokeAsync处理返回JS Promise
        • prerendering的时候无法调用JS
        • 举了一个JS TextDecoder的例子
      • JavaScript API restricted to user gestures
        • (仅限于Server)
        • 一些用户手势相关的操作(比如Fullscreen API)必须同步操作
          • 也就是用onclick,而不是@onclick
      • Invoke JavaScript functions without reading a returned value (InvokeVoidAsync)
        • 没有或者不需要返回值的话,可以用InvokeVoidAsync
        • Component (.razor) example (InvokeVoidAsync)
          • (过)
        • Class (.cs) example (InvokeVoidAsync)
          • (过)
      • Invoke JavaScript functions and read a returned value (InvokeAsync)
        • 若需要返回值,则使用InvokeAsync
        • Component (.razor) example (InvokeAsync)
          • (过)
        • Class (.cs) example (InvokeAsync)
          • (过)
        • Dynamic content generation scenarios
          • 使用 BuildRenderTree生成的动态内容,使用[Inject]属性
      • Prerendering
        • (过)
      • Synchronous JS interop in Blazor WebAssembly apps
        • (过)
      • Location of JavaScript
        • (略)
      • JavaScript isolation in JavaScript modules
        • (停)
    • Call .NET from JS (过)
    • JSImport/JSExport interop
      • 解释如何在WASM中使用.NET 7的[JSImport]以及[JSExport]交互用编口。
      • (剩余过)
  • State management
    • Server和WASM有各自适用的情形
    • (略)
  • Performance
    • Optimize rendering speed

      • Avoid unnecessary rendering of component subtrees

        • 事件发生时,下面的流程决定哪些组件需要重绘
          • 事件被派发到事件处理器所在之组件,执行完事件处理后会重绘此组件
          • 重绘的组件会提供一份新的参数拷贝给它的子组件
          • 每个子组件根据接收到的参数量值决定是否要重绘,默认情况下,参数变了之后就会重绘
        • 下面的手段可以避免递归重绘子组件树体
          • 确保子组件参数是初始非变更类型,例如string, int, bool, DateTime
          • 覆写ShouldRender
            • 用于处理非初始类型,比如自定义模型类型、事件回调、或RenderFragment量值
            • 对于UI-only的组件,首次呈现后不变,则忽略参数改动
        • 举了一个例子,如何在OnParametersSet决定是否要shouldRender
          • 事件处理器也会将shouldRender设置为true
      • Virtualization

        • (略)
      • Create lightweight, optimized components

        • (略)
        • Avoid thousands of component instances

        • Don’t receive too many parameters

        • Ensure cascading parameters are fixed

        • Avoid attribute splatting with CaptureUnmatchedValues

        • Implement SetParametersAsync manually

      • Don’t trigger events too rapidly

      • Avoid rerendering after handling events without state changes

  • Test components
    • Test approaches
      • E2E以及Unit testing
        • bUnit
        • Playwright for .NET
    • Choose the most appropriate test approach
      • Component without JS interop logic
        • Unit testing
      • Component with simple JS interop logic
        • Unit testing
      • Component that depends on complex JS code
        • Unit testing and separate JS testing
      • Component with logic that depends on JS manipulation of the browser DOM
        • E2E testing
      • Component that depends on 3rd party class library with hard-to-mock dependencies
        • E2E testing
    • Test components with bUnit
      • bUnit可以跟MSTest, NUnit以及xUnit一起协同工作
    • Additional resources

Components

  • Overview
    • Component classes
      • Razor syntax https://learn.microsoft.com/en-us/aspnet/core/mvc/views/razor?view=aspnetcore-7.0
        • Razor语法中的directive和directive attributes在Blazor中常用
          • Directives: 作用于组件,例如@page给组件指定一个URL
          • Directive attributes: 作用于组件元素,例如input元素上的@bind
      • Names
        • 文件名必须大写字母开头
        • 其他规则
          • 组件采用驼峰命名,例如ProductDetail
          • 路由采用火车命名,例如/product-detail
      • Routing
        • 通过@page指令或者RouteAttribute
      • Markup
        • 一个.razor文件被编译成一个C#标类
        • c#代码可以在一到多个@code中指定
        • C#属域可以用@方式输出,比如<h1 style="font-style:@headingFontStyle">@headingText</h1>
        • 组件在Blazor的内部表现形式是一棵渲染树体,整合了DOM和CSSDOM。此树体的改动会被反映到Web页面。
        • 组件可以放置到工程的任意位置,常见的位置是:页面放Pages,非页面放Shared
        • Razor措辞中的控制结构采用小写形式,比如@if, @code, @code。辖属为大写,比如@Body(LayoutComponentBase.Body)。
      • Asynchronous methods (async) don’t support returning void
        • 异步方法不要返回void,要返回Task
      • Nested components(过)
      • Namespaces
        • 默认的名字空间来自工程名和目录名的结合,可以用@namespace改写
        • @using可以用于打开命名空间
        • _Imports.razor中的指令对于所有.razor文件是公共的
        • 以下不受支持:
          • global::修饰符
          • 别名:@using Foo = Bar
          • 部分:@using BlazorSample,然后使用Shared.NavMenu
      • Partial class support
        • 组件编译生成的是C# partial class,所以用一个标类代码可以放在不同的文件中,比如:
          • Pages/CounterPartialClass.razor
          • Pages/CounterPartialClass.razor.cs
            • _Imports.razor中的指示对cs文件不适用
        • 组件独有的css,可以在组件名+.css结尾的文件中指定
      • Specify a base class
        • @inherits用于指定组件的基础标类,默认是ComponentBase
    • Component parameters
      • 组件参数通过公开的C#辖属来指定,并需要标有[Parameter]属性
      • 传参方式示例:
        <ParameterChild Title="Set by Parent"
                Body="@(new PanelBody() { Text = "Set by parent.", Style = "italic" })" /
        
      • 一些方针
        • The @ prefix is required for string parameters. Otherwise, the framework assumes that a string literal is set.
        • Outside of string parameters, we recommend use the use of the @ prefix for nonliterals, even when they aren’t strictly required.
        • Quotes around parameter attribute values are optional in most cases per the HTML5 specification.
      • Blazor无法在Razor表达式中执行异步动作,不支持<ParameterChild Title="@await ..." />
      • 异步动作可以在https://learn.microsoft.com/en-us/aspnet/core/blazor/components/lifecycle?view=aspnetcore-7.0#component-initialization-oninitializedasync中执行
      • 有些语法在RazorPage(.cshtml)中支持,但是在Razor Component中不支持
        • <ParameterChild Title="Set by @(panelData.Title)" />
          
      • 组件参数应是auto-properties,也就是没有自定义的set、get逻辑
      • 组件初始化之后,不要修改参数的值
      • 如果有需求对设置的参数进行转换,可以覆盖OnParametersSetAsync
      • 如果一个参数是必须的,则通过指定[EditorRequired]来提示
      • 参数支持Tuple类型,比如Tuple<int, string, bool>? Data { get; set; }
        • 目前只支持C# 7.0或以后的unnamed tuples
    • Route parameters
    • Child content render fragments
      • 组件可以通过RenderFragment来内嵌其他组件的内容,例如:
      [Parameter]
      public RenderFragment? ChildContent { get; set; }
      
      • 在循环中渲染子组件的时候,需要设一个本地变量做索引
      • 使用Enumerable.Range(0,3)则没有问题
      • 组件内容参数一般用ChildContent标出
    • Render fragments for reusable rendering logic
      • 就算不嵌套,也可以直接提取公共的RenderFragment
    • Overwritten parameters
      • 参数从父到子瀑布式传递
        • 参数不会被以外改写
        • 最小化副作用
      • 不要再子组件修改输入参数
      • 举了一个Expander例子
        • 总之,如果需要修改参数,则声明一个私有的,并在OnInitialized事件中初始化之,否则会有问题,因为这跟组件的更新逻辑有关。
    • Attribute splatting and arbitrary parameters
      • 形如<input id="useAttributesDict" @attributes="InputAttributes"/>
      • 为了捕获这样的属性,需要指定:
        [Parameter(CaptureUnmatchedValues = true)]
        public Dictionary<string, object>? InputAttributes { get; set; }
        
        • 此情形下,也可以使用 IEnumerable<KeyValuePair<string, object>>IReadOnlyDictionary<string, object>
      • 需要注意的是,Attribute splatting的顺序是从右到左,所以会被右边的覆盖
    • Capture references to components
      • 如果要添加到组件的引用
        • 添加@ref属性到子组件
        • 定义一个和子组件相同类型的属域
        • 例如<ReferenceChild @ref="childComponent" />
        • 可以调用子组件的方法,但是注意要在OnAfterRender中判断在firstRender的时候,设置好调用子组件的动作
    • Synchronization context
      • SynchronizationContext用于保证单逻辑线程执行,组件的生命周期方法和事件回调在此context执行
      • 这是为了和WASM的运行模式对齐
      • Avoid thread-blocking calls
        • 不要使用下列API
          • Result
          • Wait
          • WaitAny
          • WaitAll
          • Sleep
          • GetResult
      • Invoke component methods externally to update state
        • 通过InvokeAsync可以将任务分派到SynchronizationContext
        • 文中举了一个例子(略)
      • Use @key to control the preservation of elements and components
        • (暂略)
      • Apply an attribute
        • 例如@attribute [Authorize]
      • Conditional HTML element attributes
  • Cascading values and parameters
    • 让数据从上面节点流到下面的节点,是跨级传达,不需要逐级传达,可以允许组件跨层级
    • CascadingValue component

      • CascadingValue可以给下居节点树设置一个共享的量值
    • [CascadingParameter] attribute

      • 组件通过[CascadingParameter]来按照类型捕获共享的量值
      • 和组件参数相同,递进参数改变时也会重新呈现组件
        • CascadingValue<TValue>.IsFixed可以用于说明某递进参数是静态的
    • Cascade multiple values

      • 多个递进参数,如果类型相同,则必须指定不同的名字
      • 接收方必须指定[CascadingParameter(Name = "CascadeParam1")]
    • Pass data across a component hierarchy

      • 举了一个Tabs的例子
      • 简单地说,把一个例现作为递进参数,其方法可以供众下级调用。
  • Data binding
    • 文首
      • 数据绑定通过@bindRazor指示提供,目标既可以是域属,也可以是辖属
        • 只有UI更新的时候,这个更新的值才会呈现
        • 但是组件会在事件处理器之后触发呈现
      • 可以指定绑定到DOM事件,形如@bind:event="{EVENT}"
        • 形如<input @bind="InputValue" @bind:event="oninput" />
        • 上述在事件oninput更新InputValue
      • 执行异步逻辑的话,使用@bind:after="{EVENT}"
        • 形如<input @bind="searchText" @bind:after="PerformSearch" />
        • 意为在绑定获得更新时执行PerformSearch
      • 支持双向绑定的组件可以使用(.NET7支持的,貌似有bug)
        • @bind:get,指定用于绑定的量值
        • @bind:set,当绑定发生变化的回调
        • 例子:<input @bind:get="Value" @bind:set="ValueChanged" />
    • Multiple option selection with <select> elements
      • 支持多个选项的元素,之上的绑定可以获取一个阵列,也就是ChangeEventArgs 的Value辖属返回一个阵列
    • Binding <select> element options to C# object null values
      • 一些<select>提供不了null值的解释
    • Unparsable values
      • 如果遇到无法识别的量值,会转回前值
      • (剩余略)
    • Format strings(过)
    • Custom binding formats
      • C#辖属的get、set访问器可以用来自定义绑定的格式
      • (剩余略)
    • Binding with component parameters
      • 绑定到子组件的参数,是一个常见的场景,也叫链锁绑定
      • 措辞形如@bind-{PROPERTY}
      • 在子组件中,无法使用连锁绑定来更新上级组件的值,因为需要量值、和更新量值的事件处理要分开
      • 此事件处理器的命名约定为辖属名加上后缀Changed,类型为EventCallback,可用调用其InvokeAsync来更i性能值
      • 组件参数绑定可以有@bind:after@bind-{PROPERTY}:event
      • 事实上,下面两种写法等同
        • <ChildBind @bind-Year="year" />
        • <ChildBind @bind-Year="year" @bind-Year:event="YearChanged" />
      • 单向绑定大概是指从子组件到上级组件
    • Bind across more than two components
      • 可以上下任意嵌套,但是注意
        • 新参数值从上向下流动
        • 变更通知从下向上流动
      • 一个常见的实践是让上级保持最新值
    • Additional resources
  • Event handling
    • 文首
      • 描述事件处理的措辞:@on{DOM EVENT}="{DELEGATE}"
      • 对于事件处理委托
        • 异步的话支持返回Task
        • 自动触发UI重绘,无须呼叫StateHasChanged
        • 异常会被录记
    • Event arguments
      • Built-in event arguments
      • Custom event arguments
        • General configuration
          • 在JS中,定义一个算函来构建自定义事件参量
          • wwwroot/index.htmlPages/_Host.cshtml中注册之
          • 为事件参量定义一个C#标类
          • 通过EventHandlerAttribute来注记自定义的事件
            • 确保此EventHandler标类整体可见
          • 在HTML元素上使用自定义事件
          • 触发自定义事件的时候,bubbles必须设置为true
        • Custom clipboard paste event example
          • 一个示例
    • Lambda expressions
      • 可以用Lambda作为事件处理器
      • (其他略)
    • EventCallback
      • 用EventCallback来跨组件展露EventCallback,父组件可以设置一个回调方法到子组件的EventCallback,示例如下:
        [Parameter]
        public EventCallback<MouseEventArgs> OnClickCallback { get; set; }
        
      • 父组件中处理此事件的回调无须调用StateHasChanged,因为会自动触发此调用
      • EventCallback以及EventCallback<TValue>支持异步委托
        • 示例:await OnClickCallback.InvokeAsync(arg);
    • Prevent default actions
      • 使用形式@on{DOM EVENT}:preventDefault(等同于@on{DOM EVENT}:preventDefault="true"
        • 值也可以从属域来:<input @onkeydown:preventDefault="shouldPreventDefault" />
      • 示例:<input value="@count" @onkeydown="KeyHandler" @onkeydown:preventDefault />
    • Stop event propagation
      • 使用形式@on{DOM EVENT}:stopPropagation
      • 此指示的范围限定在Blazor的作用域,不扩展到HTML DOM。
      • 为了阻止HTML DOM事件扩散,可以:
        • 获取事件路径:Event.composedPath()
        • 根据事件标的过滤事件
    • Focus an element
      • 在元素引用上调用FocusAsync方法
  • CCS Isolation
    • 将CSS样式隔绝在单独的页面、视图中,以解决会避免
      • 维护全局样式
      • 嵌套内容的样式冲突
    • Enable CSS isolation

      • 为.razor文件创建一个对应的.razor.css文件
        • 确定是不支持在Razor代码块中导入CSS
    • CSS isolation bundling

      • 隔绝是在建构时完成的
        • 会生成类似{ASSEMBLY NAME}.styles.css的css文件
        • 目标元素上会添加b-{STRING}属性,比如<h1 b-3xxtam6d07>
        • 对应的CSS标类会对应于上述属性h1[b-3xxtam6d07]
        • 所有生成的CSS会合成到obj/{CONFIGURATION}/{TARGET FRAMEWORK}/scopedcss/projectbundle/{ASSEMBLY NAME}.bundle.scp.css
    • Child component support

      • ::deepCSS选择器可以将样式应用到子组件
        • 例如::deep h1
      • 建构时,只会改写选择器最右边的元素,例如div > adiv > a[b-{STRING}]
      • 但是指定div ::deep > a可以改写成div[b-{STRING}] > a
    • CSS preprocessor support

      • 对于sass/less这种css预处理器需要额外的配置
        • 有些NuGet料包,比如Delegate.SassBuilder可以帮助达成此目的
    • CSS isolation configuration

      • Customize scope identifier format

        • 可以自定义scope,比如在csproj的ItemGroup添加以下内容
          • <None Update="Pages/Example.razor.css" CssScope="custom-scope-identifier" />
        • 多个*.razor.css可以共享同一个custom-scope-identifier
      • Change base path for static web assets

        • csproj中PropertyGroup设置<StaticWebAssetBasePath>_content/$(PackageId)</StaticWebAssetBasePath>
      • Disable automatic bundling

        • 让第三方工具来处理发布和加载
          • <DisableScopedCssBundling>true</DisableScopedCssBundling>
      • Disable CSS isolation

        • <ScopedCssEnabled>false</ScopedCssEnabled>
    • Razor class library (RCL) support

      • 需要导入RCL中的域受限CSS:@import '_content/ClassLib/ClassLib.bundle.scp.css';
    • Additional resources

  • Lifecycle
    • 文首
      • Razor组件给出了一系列和生命周期相关的事件,籍此可以执行一些操作。
    • Lifecycle events

      • 组件命循事件
        • 如果组件是第一次呈现给一个请求
          • 创建组件现例
          • 注入辖属,执行SetParametersAsync
          • 调用OnInitialized{Async},如果返回的Task没有完成了,会等待之
        • 调用OnParametersSet{Async},如果返回的Task没有完成了,会等待之。完成后呈现组件
        • 对于所有的同步工作以及完成了Task都会进行呈现
        • 注意点
          • 反正就是有异步操作的话先呈现,等异步操作结束后再呈现
          • SetParameterAsync/OnInitialized{Async}这些只在初次呈现的时候才进行
      • 会先呈现父属组件,然后呈现子属组件,因为哪些子属组件需要呈现决定于父属组件的呈现
        • 如果父属组件的初始化是同步的,那么会确保优先完成;起步初始化的情况下,完成顺序无法确定
      • DOM事件处理
        • 跑事件处理器
        • 如果返回未完成的Task,会等待此Task完成后再呈现组件
        • 对于所有的同步工作以及完成了Task都会进行呈现
      • Render命循
        • 初次呈现后,如果ShouldRender()是彳值,则避免呈现
        • 建构呈现树的差异,呈现组件
        • 等待DOM的更新
        • 调用OnAfterRender{Async}.
      • 可以用StateHasChanged显式触发呈现
      • 代码给出更详细信息:https://github.com/dotnet/aspnetcore/blob/main/src/Components/Components/src/ComponentBase.cs
    • When parameters are set (SetParametersAsync)

      • SetParametersAsync将组件参数从父属传递到子属,组件参数包含在其ParameterView参数中
        • 驳改这个方法,可以与ParameterView中的组件参数交互
        • 默认的实现会设置[Parameter][CascadingParameter]属性的参数
        • 如果不调用base.SetParametersAsync的话,可以任意处置传入的组件参数
        • (举了一个 ParameterView.TryGetValue的例子)
    • Component initialization (OnInitialized{Async})

      • OnInitialized{Async}在SetParametersAsync之后
      • 如果在Server端预呈现内容的话,会调用OnInitializedAsync两次
        • 当组件随页面首先静态呈现的时候,在服务端执行一次
        • 当浏览器呈现组件的时候,在浏览器执行一次
      • 预呈现的时候,无法调用进JS
    • After parameters are set (OnParametersSet{Async})

      • OnParametersSet{Async}在以下情况下被调用:
        • 组件被OnInitialized{Async}初始化之后
        • 当父属组件呈现并提供
          • 某个改动过的,已知或者朴然的,非变更类型
          • 复合类型的情况,因为框架无法得知其是否被更新,于是就当作其被更新了
        • (举了一个设置时间的例子)
    • After component render (OnAfterRender{Async})

      • 在组件呈现完毕后调用,带一个firstRender参数,说明是否首次呈现
        • 适合在需要根据呈现内容来进行一些初始化的场景,比如JS操作
      • OnAfterRenderAsync如果返回未完成的任务,框架也不会等任务完成时再呈现
        • 这是为了避免陷入死循环
      • OnAfterRender和OnAfterRenderAsync在预呈现阶段不会被调用
    • State changes (StateHasChanged)

      • StateHasChanged告知框架某个组件需要再呈现
      • 会针对EventCallck方法自动执行
    • Handle incomplete async actions at render

      • 显示占位符
    • Handle errors

      • (有专门的章节)
    • Stateful reconnection after prerendering

      • Server应用其RenderMode为ServerPrerendered,此模式下组件会被作为页面的一部分,预呈现为静态内容
        • 在浏览器建立SignalR连接时,会重新呈现组件,使其可交互
      • OnInitialized{Async}会被调用两次,一种对冲的办法是在两次呈现时缓存状态
        • (举了使用Microsoft.Extensions.Caching.Memory的例子)
    • Prerendering with JavaScript interop(过)

    • Component disposal with IDisposable and IAsyncDisposable

      • 实现上述接口的组件,框架会在组件移出UI的时候执行之
        • Dispoal可以在任意时刻执行,甚至在初始化的时候
      • 如果既有同步版本又有异步版本,只会执行异步版本
      • Disposal of JavaScript interop object references(过)

      • Document Object Model (DOM) cleanup tasks during component disposal

        • 使用MutationObserver模式
      • Synchronous IDisposable

      • Asynchronous IAsyncDisposable

      • Assignment of null to disposed objects

        • 总的来说避免赋值null,但是有些情形避免不了
      • StateHasChanged

        • dispose的时候不适用
      • Event handlers

        • 取消订阅.NET事件的处理
      • Anonymous functions, methods, and expressions

        • when the object exposing the event outlives the lifetime of the component registering the delegate
        • 虽说匿名函数不太需要取消订阅,但是还有一些情况需要注意的
    • Cancelable background work

      • 举了一些需要中止任务执行的情形
      • 要中止组件的后台任务
        • 使用CancellationTokenSource以及CancellationToken
        • 在disposal的时候,调用CancellationTokenSource.Cancel
        • 异步调用返回时,调用ThrowIfCancellationRequested
      • (举了一个例子)
    • Blazor Server reconnection events

      • 断开SignalR连接后,只有UI更新会被打断。
  • Rendering
    • Rendering conventions for ComponentBase

      • 继承自ComponentBase的Razor组件,包含下面的再呈现逻辑:
        • 来自父组件参数集合的更新过后
        • 来自叠进参数的更新过后
        • 调用事件处理器响应事件之后
        • 自己调用StateHasChanged之后
      • 下列情况下会跳过再呈现
      • Control the rendering flow

      • Suppress UI refreshing (ShouldRender)

        • 返回亍值可以强制刷新(不影响初始呈现)
      • When to call StateHasChanged

        • 下面情况无需嗲用StateHasChanged
          • 对事件的处理,不管是同步的还是异步的,因为ComponentBase已经为大多数事件处理触发了再呈现
          • 典型的命周逻辑,比如OnInitalized或者OnParametersSetAsync, 因为ComponentBase已经为此触发了再呈现
        • 需要显示调用StateHasChanged的场合介绍如下
        • An asynchronous handler involves multiple asynchronous phases

        • Receiving a call from something external to the Blazor rendering and event handling system

          • 比如定时器超时的情况,需要手动指定StateHasChanged
        • To render a component outside the subtree that’s rerendered by a particular event

          • 举了一些例子
  • Class libraries
  • Bulit-in Components
    • Blazor自带的组件看这里。

其他文章

Prevent refreshing the UI after an event in Blazor

  • 默认情况下,Blazor会在事件处理器之后调用StateHasChanged,如果事件处理器是异步的,那么可能会调用俩次
  • 此行为出现在ComponentBase标类中source
  • 解决方案是通过实现IHandleEvent提供一个回调,那么Blazor就会使用自定义的回调,相关实现在sourc
    • 例子
      private record SimpleCallback(Action Callback) : IHandleEvent
      {
          public static Action Create(Action callback) => new SimpleCallback(callback).Invoke;
          public static Func<Task> Create(Func<Task> callback) => new SimpleAsyncCallback(callback).Invoke;
      
          public void Invoke() => Callback();
          public Task HandleEventAsync(EventCallbackWorkItem item, object arg) => item.InvokeAsync(arg);
      }
      
      private record SimpleAsyncCallback(Func<Task> Callback) : IHandleEvent
      {
          public Task Invoke() => Callback();
          public Task HandleEventAsync(EventCallbackWorkItem item, object arg) => item.InvokeAsync(arg);
      }
      

其他参考

资源

(本篇完)