浏览器 -->> 服务器: 建立 WebSocket 连接
服务器 -->> 浏览器: 发送首页 HTML 代码
loop 连接未断开
Note left of 浏览器: 浏览器JS捕获用户输入事件
浏览器 -->> 服务器: 通知服务器发生了该事件
Note right of 服务器: 服务器 .Net 处理事件
服务器-->>浏览器: 发送有变动的 HTML 代码
Note left of 浏览器: 浏览器JS渲染变动的 HTML 代码
end
备注:1. WebSocket 连接采用 SignalR 来建立,如果浏览器不支持 WebSocket,SignalR 会采用其他技术建立。(SignalR是重点,可自行搜索一下)
当我们通过 NavigationManager 去改变路由地址时,大概流程如下
st=>start: 服务器启动
rt=>operation: 初始化 Router 组件,Router 内部注册 LocationChanged 事件
op1=>operation: LocationChanged 事件中根据路由查找对应的组件,默认触发首页组件
queue=>operation: 加入渲染队列
render=>operation: 一直进行渲染及比对,直到队列中所有的组件全部渲染完
diff=>operation: 将比对的差异结果更新至浏览器
e=>end: 等待下一次路由改变,继续触发 LocationChanged 事件
st->rt->op1->queue->render->diff->e
组件是用户界面(UI)的自包含部分,具有支持动态行为的处理逻辑。 组件可以在项目之间嵌套、重用、共享,并在MVC和Razor Pages应用中使用。
在Razor组件文件中,组件是使用c#和HTML标记的组合来实现的,扩展名为. Razor。
从编程的角度来看,组件只是一个实现了IComponent接口的类。 仅此而已。 当它被附加到RenderTree (Renderer用来构建和更新的组件树)上时,它就有了生命。 UI IComponent接口是“Renderer”用来与组件通信和接收组件通信的接口
public interface IComponent
{
void Attach(RenderHandle renderHandle);
Task SetParametersAsync(ParameterView parameters);
}
我看到这个的第一反应是“什么? 漏掉了一些东西。 那些事件和初始化方法在哪里?” 你读过的每一篇文章都在谈论组件和OnInitialized,… 别让他们把你弄糊涂了。 这些都是ComponentBase的一部分,即IComponent的开箱即用的Blazor实现。 ComponentBase没有定义组件。 你将在下面看到一个简单得多的实现。
Blazor Hub Session有一个Renderer,它为每个根组件运行RenderTree。 从技术上讲,你可以有多个(根组件),但我们将在本文中忽略这一点。 我们刨析一下上面这个接口定义中的一些细节:
Rederer提供了如下机制:
RenderHandle结构:允许组件与其渲染器(Renderer)交互。
再回到IComponent这个接口上来:
注意,IComponent没有RenderTree的概念。 它通过调用SetParametersAsync来触发,并通过调用RenderHandle上的方法来传递更改。
下图是Blazor模板的渲染树(Render Tree)的可视化表示
● OnInitialized、OnInitializedAsync:仅在第一次实例化组件时,才会调用这些方法一次。注意,该方法调用时参数已经设置,但没有渲染。
● SetParametersAsync:该方法可以让您在设置参数之前做一些事
● OnParametersSetAsync、OnParametersSet:每一次参数设置完成之后都会调用
● OnAfterRender、OnAfterRenderAsync:在组件渲染完成之后触发
● ShouldRender:如果该方法返回 false,则组件在第一次渲染完成后不会执行二次渲染
● StateHasChanged:强制渲染当前组件,如果 ShouldRender 返回的是 false,则不会强制渲染
● BuildRenderTree: 该方法一般情况下我们用不到,它的作用是拼接 HTML 代码,由 VS 自动生成的代码去调用它
另有一个关键的结构体 EventCallBack,还有一个关键的委托RenderFragment,它俩非常重要,前者可能见得比较少,后者基本上都知道。
st=>start: 开始渲染
isfirst=>condition: 是否首次渲染
init=>operation: 调用 OnInitialized 方法
initAsync=>operation: 调用 OnInitializedAsync 方法
onSetParameter=>operation: 调用 OnParametersSet 方法
setParameter=>operation: 调用 SetParametersAsync 方法
stateHasChanged=>operation: 调用 StateHasChanged 方法
st->setParameter->isfirst->init->initAsync->onSetParameter
onSetParameter->stateHasChanged
isfirst(yes)->init
isfirst(no)->onSetParameter
需要注意的是这个流程中没有 OnAfterRender 方法的调用,这个将在下面讨论
这个方法至关重要,就比如上图中最终只到了 StateHasChanged 方法,就没了下文,我们来看看这个方法里面有什么
st=>start: 开始
isfirst=>condition: 是否首次渲染
should=>condition: ShouldRender 为True?
queue=>operation: 进入渲染队列
render=>operation: 开始循环渲染队列的数据
after=>operation: 触发 OnAfterRender 方法
e=>end: 结束
st->isfirst
queue->render->after->e
isfirst(yes)->queue
isfirst(no)->should
should(yes)->queue
should(no)->e
至此,我们基本把一个组件的生命周期的那几个方法讨论完了,除了一些异步版本的,逻辑都差不多,没有写进来
渲染队列的工作:
st=>start: 开始渲染队列
queue=>condition: 队列还有组件?
read=>operation: 从队列获取组件
swap=>operation: 备份当前 DOM 树及清空
render=>operation: 调用组件的 RenderFragment 委托获取新的 DOM 树
diff=>operation: 与备份的树对比
append=>operation: 将对比结果存入列表
display=>operation: 将列表中的所有对比结果发送至浏览器
e=>end: 结束
st->queue
read->swap->render->diff->append->queue
queue(yes)->read
queue(no)->display->e
几点注意
blazor 如何对比的呢?
st=>start: 开始对比
seq=>operation: 循环每帧
compare=>condition: 序列号是否一致?
isComponent=>condition: 该帧是否都为组件?
render=>operation: 渲染该组件
compareParameter=>condition: 两边组件的参数是否有变化?
skip=>operation: 跳过该帧
setParameter=>operation: 设置新组件的参数,进入该组件的生命周期流程
currentSkip=>operation: 机制过于复杂,不讨论
e=>end: 对比结束
endSeq=>operation: 结束循环
st->seq->compare
compare(yes)->isComponent
compare(no)->currentSkip
isComponent(yes)->render->compareParameter
isComponent(no)->currentSkip
compareParameter(yes)->setParameter->endSeq->e
compareParameter(no)->skip
浏览器产生任何事件都会发送到服务器端,想象一下你注册了一个 onmousemove 事件的话,还要不要活了?所以,大规模触发的事件尽量少注册,这里面的网络传输成本是很大的,而且也会给你的服务端造成很大的压力。
Blazor 应用变卡一般有以下几种情况,我们只讨论服务端应用的情况
出现了卡的情况,会非常头疼,但实际上大多数情况都是第二种中
结合所有流程图来看,Blazor 完成渲染才会发送至浏览器,那么完成渲染的标准就是渲染队列被清空,那如果一直无法清空呢?体现出来就是死循环,或者说发生了一次点击事件结果循环了十次,这明显不科学(你故意的例外),而渲染队列被加入新东西大多数情况下是因为调用了 StateHasChanged 并且 ShuoldRender 返回了 true,或者是因为使用了 EventCallBack,这些代码所在的地方你全都难以调试
因为这些代码不是你的代码,所以你的断点也没处打,目前的 Blazor 不会告诉你到底是哪个组件哪行代码引起的死循环。
大部分来自于网上,没有找到来处,特此声明。