当初为什么要折腾 Fun.Blazor 呢?直接用 C# 写,按照微软官方指导不香吗?当 C# 很多应用场景都支持开发时 hot-reload 的时候,而 F# 官方一直又没有什么说法,我是纠结和后悔过的。自己挖的坑,自己还是得填啊,后来自己也倒腾了一个简单的 hot-reload 之后,再回头来看,其实也没那么糟糕,至少很多事情最终按照我理想中的样子成型了。
我理想中的界面 DSL
- UI 控件的使用要简练,不冗余,没有任何模板语法,只有函数和数据
- 能做独立组件,以隔离自己的状态
- 能有显示的依赖注入
- 数据不变,UI不变
- 能有一个适应性的全局状态共享中心
- 能抽象业务逻辑,以重用或减少代码复杂度
// 在 F# 里变量名函数名等都可以用中文的,用 `` `` 引起来就好了
type ``模拟数据`` = { Length: int }
type IShareStore with
member store.``共享数据`` =
store.CreateCVal(nameof store.``共享数据``, LoadingState<``模拟数据``>.NotStartYet)
type IComponentHook with
member hook.``加载数据``() =
let store, http = hook.ServiceProvider.GetMultipleServices<IShareStore * MyHttpClient>()
store.``共享数据``.Publish LoadingState.start
http.Get<``模拟数据``>("/api/data/demo")
|> Task.map (LoadingState.ofResult >> store.``共享数据``.Publish)
|> ignore
let ``演示页面`` =
html.inject (fun (store: IShareStore, hook: IComponentHook) ->
let ``本地数据`` = ChangeableValue 0
hook.OnFirstAfterRender.Add hook.``加载数据``
let ``数据视图`` =
adaptiview () { // 适应性的,只有当 data 内容变更才会重新渲染
match! store.``共享数据`` with
| LoadingState.NotStartYet ->
button {
onclick (fun _ -> hook.``加载数据``())
"加载数据"
}
| LoadingState.Loading ->
p { "数据加载中。。。" }
| LoadingState.Loaded result ->
p { $"数据总共有 {result.Length} 条" }
| LoadingState.Reloading result ->
p { $"数据总共有 {result.Length} 条" }
p { "数据重载中。。。" }
}
let ``计数器`` =
adaptiview () {
let! count = ``本地数据``
div { $"计数值 = {count}" }
}
let ``计数器按钮`` =
button {
onclick (fun _ -> ``本地数据``.Publish (fun x -> x + 1))
"自增 1"
}
div {
h2 {
style { color "green" }
"测试数据集"
}
``数据视图``
``计数器``
``计数器按钮``
}
)
let ``主应用`` =
div {
header { "页面头部" }
main {
html.route [
routeCi "/demo" ``演示页面``
routeAny (div { "没有找到相应页面!!!" })
]
}
footer { "页面尾部" }
}
开发时 hot-reload
我是基于两年前别人做的一个社区项目改造而成,目前只能说简单可用,但是确实和 C# 的实现还是有一定的差距,就更别说和 React 比了。不过 C# 的实现其实也不怎么样,目前还不太稳定和可靠,随着项目的的增加,速度也明显下降。
对于我来说,hot-reload 主要就是调整界面细节,所以通常如果有大的更改,比如新建文件,重构,业务逻辑修改等,我都会 F5 重启一次。所以还可以(也只能)忍受。
最后
Fun.Blazor 版本2基本也就这样了,学了不少东西,也实现了不少东西,现在开始在兼职的项目中使用,虽然开发的工具链(hot-reload)不怎么样,但是总体出来的代码质量好了不少,后期的维护也简单了很多,所以可以在正式版发布后,告以段落。是时候换换口味了!