文章目录
前言
用 razor
的方式编写组件,这个学习过前端的应该都可以完成,但很多时候,我们需要学习更加高深的技术,不光是可以来装X,更是可以把X升级成高处不胜寒的王者。所以就来教教如何使用纯代码的方式来编写组件。
在装X之前,先了解《探索razor组件背后的秘密,你绝对想象不到》。
装X大法——RenderTreeBuilder
用最常用的例子来说明,编写一个按钮组件。
用 razor
页面来编写组件:
<button class="btn btn-primary" @onclick="OnClick">
@ChildContent
</button>
@code {
[Parameter]public RenderFragment ChildContent { get; set; }
[Parameter]public EventCallback<MouseEventArgs> OnClick { get; set; }
}
现在开始装X大法:
之前说过组件是一个类。
1. 创建组件类
先创建一个类,并继承 ComponentBase
基类,然后声明一些组件需要的参数:
public class MyButton : ComponentBase
{
[Parameter]public RenderFragment ChildContent { get; set; }
[Parameter]public EventCallback<MouseEventArgs> OnClick { get; set; }
}
2. 重写 BuildRenderTree 方法
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
//... 这里要写组件
}
重点就在如何使用 RenderTreeBuilder
对象。写法其实就是按照平时写 html 元素的顺序进行。
OpenElement
如果我需要写一个元素 button ,我的写法是
<button>...</button>
写一个开始标记和一对结束标记,然后再反过来填充元素里面的内容。
在代码中,使用 builder.OpenElement
表示开始元素,使用 builder.CloseElement
表示结束元素
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenElement(0, "button");
//...
builder.CloseElement();
}
系统会检测 CloseElement
所匹配的 OpenElement
所生成的元素。
AddAttribute
使用 builder.AddAttribute
给元素添加属性,也就是 <button 属性="值">...</button>
,当然 class
也是属性。
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenElement(0, "button");
builder.AddAttribute(1, "class", "btn btn-primary");
builder.CloseElement();
}
使用 builder.AddContent
添加元素内的内容,即 <button>内容</button>
。
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenElement(0, "button");
builder.AddAttribute(1, "class", "btn btn-primary");
builder.AddContent(2, ChildContent);
builder.CloseElement();
}
调用顺序必须是按照真实的 html 写法,以下方式会报错:
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenElement(0, "button");
builder.AddContent(1, ChildContent);
builder.AddAttribute(2, "class", "btn btn-primary");
builder.CloseElement();
}
InvalidOperationException: Attributes may only be added immediately after frames of type Element or Component
系统会认为,如果你已经决定添加了元素之间的内容,就表示开始元素已经结束(即形成了 >
)。
EventCallback
使用
EventCallback
类来创建触发某个 html 事件并绑定到你设置的参数OnClick
中。
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenElement(0, "button");
builder.AddAttribute(1, "class", "btn btn-primary");
builder.AddAttribute(2, "onclick", EventCallback.Factory.Create(this, OnClick));
builder.AddContent(3, ChildContent);
builder.CloseElement();
}
3. 最后的结果
@inject IJSRuntime JS
<MyButton OnClick="@(e=>JS.InvokeVoidAsync("alert","点击了按钮"))">按钮</MyButton>
没有页面,只有代码的方式,就问你看到这样的源码屌不屌呢?
关于序列号
你会看到每次调用一个方法,都会要求有一个 sequence
参数,称为序列号。该参数与代码行号相关,而不与执行顺序相关。
Razor 组件文件 (
.razor
) 始终被编译。 与解释代码相比,编译具有潜在优势,因为编译步骤可用于注入信息,从而在运行时提高应用性能。
这些改进的关键示例涉及序列号。 序列号向运行时指示哪些输出来自哪些不同的已排序代码行。 运行时使用此信息在线性时间内生成高效的树上差分,这比常规树上差分算法通常可以做到的速度快得多。
以下是关于系列号的建议和指南:
- 如果动态生成序列号,则应用性能会受到影响。
- 该框架无法在运行时自动创建自己的序列号,因为除非在编译时捕获了必需的信息,否则这些信息不存在。
- 不要编写手动实现的冗长
RenderTreeBuilder
逻辑块。 优先使用 .razor 文件并允许编译器处理序列号。 如果无法避免RenderTreeBuilder·
手动逻辑,请将较长的代码块拆分为封装在OpenRegion/CloseRegion
调用中的较小部分。 每个区域都有自己的独立序列号空间,因此可在每个区域内从零(或任何其他任意数)重新开始。 - 如果序列号已硬编码,则差分算法仅要求序列号的值增加。 初始值和间隔不相关。 一个合理选择是使用代码行号作为序列号,或者从零开始并以 1 或 100 的间隔(或任何首选间隔)增加。
- Blazor 使用序列号,而其他树上差分 UI 框架不使用它们。 使用序列号时,差分速度要快得多,并且 Blazor 的优势在于编译步骤可为编写 .razor 文件的开发人员自动处理序列号。
关于序列号的更多内容可参考微软官方文档
结论
用代码的方式创建组件固然很牛,但是也比较影响开发效率,而且也提升了一个人对于 html 知识的高度,你对组件、页面布局要有很强的具象思维,特别对于复杂组件。但对于个人来说,确实是实力的展现,同样也需要付出更多。装X有风险,且装且珍惜!