Blazor University (40)JavaScript 互操作 —— 传递 HTML 元素引用

原文链接:https://blazor-university.com/javascript-interop/calling-javascript-from-dotnet/passing-html-element-references/

传递 HTML 元素引用

源代码[1]

在编写 Blazor 应用程序时,不鼓励对文档对象模型 (DOM) 进行操作,因为它可能会干扰其增量渲染树[2],对 HTML 的任何更改都应在我们组件内的 .NET 代码中进行管理。

有时我们可能希望继续让 JavaScript 与我们生成的 HTML 交互。实现这一点的标准 JavaScript 方法是给我们的 HTML 元素一个 id,并让 JavaScript 使用 document.getElementById('someId') 来定位它。在静态生成的 HTML 页面中,这非常简单,但是当通过组合许多组件的输出来动态创建页面时,很难确保 ID 在所有组件中都是唯一的。Blazor 使用 @ref 元素标记和 ElementReference 结构解决了这个问题。

@ref 和元素引用

当我们需要对 HTML 元素的引用时,我们应该使用 @ref 装饰该元素(或 Blazor 组件)。我们通过创建一个类型为 ElementReference 的成员并使用 @ref 属性在元素上识别它来识别我们组件中的哪个成员将持有对 HTML 元素的引用。

@page "/"<h1 @ref=MyElementReference>Hello, world!</h1>
Welcome to your new app.@code {ElementReference MyElementReference;
}
  • 第 3 行

    定义一个 HTML 元素并使用 @ref 指定在引用该元素时我们将使用组件中的哪个成员 (MyElementReference)。

  • 第 7 行

    引用用 @ref 装饰的元素时将使用的成员。

如果我们更改新 Blazor 应用程序的 Index.razor 文件以添加对 h1 元素的元素引用并运行应用程序,我们将看到类似于以下生成的 HTML 的内容。

<h1 _bl_bc0f34fa-16bd-4687-a8eb-9e3838b5170d="">Hello, world!</h1>

添加此特殊格式的属性是 Blazor 如何唯一标识元素而无需劫持元素的 id 参数。我们现在将使用 @ref``、ElementReference 和 JavaScript 互操作来解决一个常见问题。

案例:元素自动聚焦

HTML 规范有一个 autofocus 属性,可以应用于任何可聚焦的元素;当一个页面被加载时,浏览器会找到第一个用 autofocus 装饰的元素并给它焦点。由于 Blazor 应用程序不会真正导航(HTML 被简单地重写并且浏览器 URL 更改),当我们导航到新 URL 并向用户呈现新内容时,浏览器不会扫描 autofocus 属性。这意味着将 autofocus 属性放在输入上不起作用。这是我们将使用 JavaScript Interop、@refElementReference 解决的问题。

观察自动聚焦问题

  • 首先创建一个新的 Blazor 应用程序。

  • 在每个页面中,用每个 @page 指令下方的相同标记替换内容。

Enter your name: <input autofocus />

运行应用程序并观察 <input> 元素如何不会自动获得焦点,甚至在第一页加载时也不会。

解决自动聚焦问题

  • wwwroot 文件夹中创建一个脚本文件夹。

  • 在该文件夹中创建一个名为 AutoFocus.js 的新文件并输入以下脚本。

var BlazorUniversity = BlazorUniversity || {};
BlazorUniversity.setFocus = function (element) {element.focus();
};

确保在 /Pages/_Host.cshtml(服务器端 Blazor 应用程序)或 /wwwroot/index.html(WebAssembly Blazor 应用程序)中添加对此脚本的引用。

Index.razor 页面中更改标记如下:

@page "/"
@inject IJSRuntime JSRuntime
Enter your name
<input @ref=ReferenceToInputControl />@code
{ElementReference ReferenceToInputControl;protected override async Task OnAfterRenderAsync(bool firstRender){if (firstRender)await JSRuntime.InvokeVoidAsync("BlazorUniversity.setFocus", ReferenceToInputControl);}
}
  • 第 4 行

    使用 @ref 装饰器为输入提供一个在组件内唯一的标识。

  • 第 8 行

    这是将持有元素标识的成员,该成员必须是 ElementReference 类型。

  • 第 12 行

    如果这是该组件第一次渲染,则元素引用将传递给我们的 JavaScript,它为元素提供焦点。

现在,在页面之间切换应该会导致第一页上的输入在呈现特定页面时获得焦点。

组件化我们的自动聚焦解决方案

添加 JavaScript 以在每个页面上设置焦点并不需要太多工作,但它是重复的。此外,根据显示的选项卡将自动对焦设置为选项卡控件中的第一个控件将需要更多工作。这是我们应该以可重用的形式编写的那种东西。

首先,更改我们其中一个页面的标记,使其使用新的 AutoFocus 控件。

@page "/"
Enter your name
<input @ref=ReferenceToInputControl />
<AutoFocus Control=ReferenceToInputControl/>@code {ElementReference ReferenceToInputControl;
}

/Shared 文件夹中创建一个名为 Autofocus.razor 的新组件并输入以下标记。

@inject IJSRuntime JSRuntime
@code {[Parameter]public ElementReference Control { get; set; }protected override async Task OnAfterRenderAsync(bool firstRender){if (firstRender)await JSRuntime.InvokeVoidAsync("BlazorUniversity.setFocus", Control);}
}
  • 第 4 行

    为组件定义一个参数 Control,该参数接受一个 ElementReference 来标识哪个控件应该获得焦点。

  • 第 9 行

    执行我们的 JavaScript 以将焦点设置到指定的控件。

这个解决方案的问题在于,组件参数的值是在渲染树构建过程中传递的,而元素引用在构建渲染树并且结果已经在浏览器中渲染为 HTML 之后才有效。此解决方案导致错误 element.focus is not a function,因为 ElementReference 在其值被传递给我们的 AutoFocus 组件时无效。

注意:不要过早使用元素引用!

正如我们在渲染树[3]部分中看到的,在其渲染阶段,Blazor 根本不会更新浏览器 DOM。只有在所有组件的渲染完成后,Blazor 才会比较新的和以前的渲染树,然后用尽可能少的更改更新 DOM。

这意味着在构建渲染树时,使用 @ref 引用的元素可能还不存在于浏览器 DOM 中——因此任何通过 JavaScript 与它们交互的尝试都将失败。因此,我们不应该尝试在除 OnAfterRenderOnAfterRenderAsync 之外的任何组件生命周期方法中使用 ElementReference 的实例,并且由于组件的参数是在构建渲染树期间设置的,我们不能将 ElementReference 作为参数传递,因为它是在组件的生命周期中为时过早。当然,从用户事件(例如按钮单击)访问引用是可以接受的,因为该页面已经生成为 HTML。

事实上,直到调用 OnAfterRender* 方法之前,甚至不会设置 ElementReference 的实例。Blazor 流程如下:

  • 为页面生成虚拟渲染树。

  • 将更改应用到浏览器的 HTML DOM。

  • 对于每个 @ref 修饰元素,更新 Blazor 组件中的 ElementReference 成员。

  • 执行 OnAfterRender* 生命周期方法。

我们可以通过更改标准 Blazor 应用程序的 Index.razor 组件来证明这个过程,在组件生命周期的各个点将 ElementReference 序列化为字符串,并将序列化的文本呈现到屏幕上。将新项目中的 Index.razor 更改为以下标记并运行应用程序。

@page "/"<h1 @ref=MyElementReference>Hello, world!</h1>
<button @onclick=ButtonClicked>Show serialized reference</button><code><pre>@Log</pre></code>Welcome to your new app.@code {string Log;ElementReference MyElementReference;protected override void OnInitialized(){Log += "OnInitialized: ";ShowSerializedReference();}protected override void OnAfterRender(bool firstRender){Log += "OnAfterRender: ";ShowSerializedReference();}private void ButtonClicked(){Log += "Button clicked: ";ShowSerializedReference();}private void ShowSerializedReference(){Log += System.Text.Json.JsonSerializer.Serialize(MyElementReference) + "\r\n";}
}
  1. 我们的组件实例已创建。执行 OnInitialized(第 15 行)。

  2. MyElementReference 的值被序列化为我们的 Log 字符串(第 33 行)。

  3. 生成渲染树。

  4. 浏览器的 DOM 已更新

  5. Blazor 检查使用 @ref 修饰的元素并更新它们标识的 ElementReference

  6. OnAfterRender 在我们的组件上执行(第 21 行)。

  7. MyElementReference 的值被序列化为我们的 Log 字符串,但不显示 - 我们必须调用 StateHasChanged 才能看到它,但 Log 的值已经更新。

  8. 用户单击按钮。

  9. MyElementReference 的值被序列化为我们的 Log 字符串。

  10. Blazor 执行 StateHasChanged 以响应按钮单击。

  11. 我们在屏幕上看到更新的 Log 以显示从第 7 步和第 9 步添加的值——这两个都显示了一个非空标识符。

edcda5b98bbd90176f21dc084601626f.gif

完成 AutoFocus  组件

我们可以传入一个 Func<ElementReference>,而不是传入 ElementReference 本身,我们的 AutoFocus 组件然后可以在其 OnAfterRender* 生命周期方法中执行此 Func——此时返回的值将是有效的。

AutoFocus 控件更改为接受 Func,并确保设置的值不为空。

@inject IJSRuntime JSRuntime
@code {[Parameter]public Func<ElementReference> GetControl { get; set; }protected override async Task OnAfterRenderAsync(bool firstRender){if (GetControl is null)throw new ArgumentNullException(nameof(GetControl));if (firstRender)await JSRuntime.InvokeVoidAsync("BlazorUniversity.setFocus", GetControl());}
}

该组件现在可以按如下方式使用:

@page "/"
Enter your name
<input @ref=ReferenceToInputControl />
<AutoFocus GetControl=@( () => ReferenceToInputControl)/>@code {ElementReference ReferenceToInputControl;
}

注意:未来的 Blazor 计划自动创建 ElementReference 成员。

参考资料

[1]

源代码: https://github.com/mrpmorris/blazor-university/tree/master/src/JavaScriptInterop/HtmlElementReferences

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/284010.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RabbitMQ+PHP 教程六(RPC)

(using php-amqplib) 前提必读 本教程假设RabbitMQ是安装在标准端口上运行&#xff08;5672&#xff09;。如果您使用不同的主机、端口或凭据&#xff0c;则连接设置需要调整。 如果您在本教程中遇到困难&#xff0c;可以通过邮件列表与我们联系。 开始 在第二个教程中&#xf…

TKMybatis 介绍和使用

目录 一、什么是 TKMybatis 二、TKMybatis 使用 2.1 Springboot 项目中加入依赖 2.2 使用讲解 2.2.1 实体类中使用 2.2.2 dao中使用 2.2.3 Service 层中使用 2.3 实际案例 2.3.1 dao 层使用 2.3.2 service 层使用 一、什么是 TKMybatis TKMybatis 是基于 Mybatis 框…

WinForm(三)揭开可视化控件的面纱

WinForm所见即所得的UI设计框架&#xff0c;开发效率确实有所提升&#xff0c;同时降低了编程门槛&#xff0c;让WinForm更普及。拖拖拽拽就能设计出一个界面&#xff0c;那么我们拖拽的这些东西是什么&#xff1f;它们是什么原理&#xff1f;。WinForm我觉得很好的一点是&…

RestTemplate 详解

在项目中&#xff0c;当我们需要远程调用一个 HTTP 接口时&#xff0c;我们经常会用到 RestTemplate 这个类。这个类是 Spring 框架提供的一个工具类。Spring 官网对它的介绍如下&#xff1a; RestTemplate: The original Spring REST client with a synchronous, template met…

初识Spark2.0之Spark SQL

内存计算平台Spark在今年6月份的时候正式发布了spark2.0&#xff0c;相比上一版本的spark1.6版本&#xff0c;在内存优化&#xff0c;数据组织&#xff0c;流计算等方面都做出了较大的改变&#xff0c;同时更加注重基于DataFrame数据组织的MLlib&#xff0c;更加注重机器学习整…

ABP详细教程——模块类

概述模块化是ABP vNext的最大亮点&#xff0c;也是ABP vNext框架的核心&#xff0c;而模块类是ABP vNext框架模块化的核心要素。这一章节&#xff0c;我就从模块类的用法、运行机制、源代码等层面&#xff0c;带大家详细了解ABP vNext的模块类。用法在ABP的约定中&#xff0c;每…

[转]Eureka工作原理

目录 Eureka 工作原理 Eureka 核心概念 自我保护机制 Eureka 集群原理 Eurka 工作流程 总结 Eureka 工作原理 上节内容为大家介绍了&#xff0c;注册中心 Eureka 产品的使用&#xff0c;以及如何利用 Eureka 搭建单台和集群的注册中心。这节课我们来继续学习 Eureka&…

重谈联想5G编码投票事件

此前&#xff0c;司马南谈了联想好几个问题&#xff0c;其中最尖锐的要属国有资产流失&#xff0c;这是联想管理层无法回避的死穴。不过&#xff0c;司马南批判联想5G投票背刺H公司&#xff0c;这基本就是造谣了。当年&#xff0c;媒体把编码投票炒作的很厉害&#xff0c;抨击联…

JStorm2.1.1集群的安装和使用

为什么80%的码农都做不了架构师&#xff1f;>>> JStorm2.1.1集群的安装和使用 Storm是一个免费开源、分布式、高容错的实时计算系统&#xff0c;而JStorm是阿里巴巴开源的基于Storm采用Java重写的一套分布式实时流计算框架&#xff0c;在性能和支持的集群规模上做了…

Hystrix 原理

Hystrix是什么&#xff1f; Hystrix是Netflix开源库&#xff0c;这是一个针对分布式系统的延迟和容错库。 Hystrix 供分布式系统使用&#xff0c;提供延迟和容错功能&#xff0c;隔离远程系统、访问和第三方程序库的访问点&#xff0c;防止级联失败&#xff0c;保证复杂的分布…

「深度」无人机实名制政策特稿|市场看好、资本关注,“反黑飞”正在崛起

从政策和需求来看&#xff0c;“反黑飞”越来越重要&#xff0c;市场也正在不断崛起。 对于大多数人来说&#xff0c;今天是最适合明目张胆“装嫩”的六一儿童节。不过&#xff0c;在无人机厂商和无人机玩家的眼里&#xff0c;今天是无人机实名制政策正式实施的日子。 近年来&…

在navicat中新建数据库

前言&#xff1a; 在本地新建一个名为editor的数据库&#xff1b; 过程&#xff1a; 1.&#xff1b; 2.选择&#xff1a;utf8mb4 -- UTF-8 Unicode字符集&#xff0c;原因在于&#xff1a;utf8mb4兼容utf8&#xff0c;且比utf8能表示更多的字符。&#xff0c;而且它支持表情符号…

MASA Stack 第三期社区例会

MASA Blazor 0.5.0发版内容功能Autocomplete&#xff1a;支持通过设置AutoSelectFirst参数开启自动选择第一项的功能&#xff0c;支持CacheItems参数&#xff0c;增强使用上下键的用户体验。BottomNavigation&#xff1a;&#xff1a;一个替代侧边栏的新组件。它主要用于移动应…

[转]高并发架构设计之--「服务降级」、「服务限流」与「服务熔断」

目录 服务降级 1 、简介 2 、使用场景 3 、核心设计 3.1 分布式开关 3.2 自动降级分类 3.3 配置中心 3.4 处理策略 3.5 降级分类 3.6 服务降级要考虑的问题 4 、高级特性 4.1 分级降级 4.2 降级权值 5 、总结与展望 服务限流 一、为什么要做服务限流设计&…

SpringBoot获取ApplicationContext

2019独角兽企业重金招聘Python工程师标准>>> 有两种方法&#xff1a; 创建Component实现ApplicationContextAware接口&#xff0c;SpringBoot会自动调用这个类的setApplicationConext()方法。鼓励使用这种方式。SpringApplication.run(MyApplication.class, args)这…

SkiaSharp 之 WPF 自绘 投篮小游戏(案例版)

此案例主要是针对光线投影法碰撞检测功能的示例&#xff0c;顺便做成了一个小游戏&#xff0c;很简单&#xff0c;但是&#xff0c;效果却很不错。投篮小游戏规则&#xff0c;点击投篮目标点&#xff0c;就会有一个球沿着相关抛物线&#xff0c;然后&#xff0c;判断是否进入篮…

zuul集成ribbon完成服务通信和负载均衡

目录 Zuul2服务通信 超时相关 默认超时配置 自定义超时配置 负载均衡 Zuul2服务通信 描述&#xff1a;zuul2通过Ribbon完成客户端负载均衡以及与服务器群集进行通信。 zuul2的通信是集成Ribbon实现的&#xff0c;在Origin中集成Ribbon基本配置&#xff08;例如IClientCo…

时任上海来伊份互联网事业群总裁王戈钧 :传统企业(线上+线下)移动互联网改造...

2017年12月22日-23日&#xff0c;第13届信息化领袖峰会暨2017中国数字化贡献人物颁奖盛典在上海盛大开幕。本次峰会由上海市经济和信息化委员会指导&#xff0c;上海市国有资产信息中心、上海市计算机用户协会、上海市信息服务业行业协会、上海大数据联盟、上海市高等教育学会支…

【.NET6+Modbus】Modbus TCP协议解析、仿真环境以及基于.NET实现基础通信

接下来的内容&#xff0c;我会以从头开发一个简单的基于modbus tcp通信的案例&#xff0c;来实现一个基础的通信功能。有关环境&#xff1a;开发环境&#xff1a;VS 2022企业版运行环境&#xff1a;Win 10 专业版.NET 环境版本&#xff1a;.NET 6【备注】 源码在文末 1、新建一…

源码深度剖析Eureka与Ribbon服务发现原理

本文基于 spring cloud dalston&#xff0c;同时文章较长&#xff0c;请选择舒服姿势进行阅读。 Eureka 与 Ribbon 是什么&#xff1f;和服务发现什么关系&#xff1f; Eureka 与 Ribbon 都是 Netflix 提供的微服务组件&#xff0c;分别用于服务注册与发现、负载均衡。同时&a…