如何优雅的移植JavaScript组件到Blazor

如何优雅的移植JavaScript组件到BlazorBlazor 作为一个新兴的交互式 Web UI 的框架 有其自身的优缺点 如果现有的 JavaScript 组件能移植到 Blazor 无疑让 Blazor 如虎添翼 本文就介绍一下自己在开发 BulmaRazor 组件库的时 封装现有的 JavaScript 组件的方法 文中以 TuiEditor 为例

大家好,我是讯享网,很高兴认识大家。

Blazor作为一个新兴的交互式 Web UI 的框架,有其自身的优缺点,如果现有的 JavaScript 组件能移植到 Blazor,无疑让 Blazor 如虎添翼,本文就介绍一下自己在开发 BulmaRazor 组件库的时,封装现有的 JavaScript 组件的方法,文中以 TuiEditor 为例。

开始

首先找到现有 TuiEditor 的主页或者文档,这一步很简单,我们找到官网 https://ui.toast.com/tui-editor/ ,分析一下组件的使用方法,一般都是有样式文件,有 JavaScript 文件,有一个 options 对象来初始化一个主对象,主对象上有方法和事件,大概就是这些了,我们先下载所需的文件,然后一步一步处理。

样式部分

该组件需要两个样式 codemirror.min.css 和 toastui-editor.min.css ,由于一个组件库不只这一个组件,为了引用方便,我们需要使用 BuildBundlerMinifier 合并文件,不知道 BuildBundlerMinifier 的同学网上查一下。

讯享网
在网站的根目录需要有 BuildBundlerMinifier 所需的配置文件 bundleconfig.json,对应的配置如下 :

 { "outputFileName": "wwwroot/bulmarazor.min.css", "inputFiles": [ "wwwroot/css/tuieditor/codemirror.min.css", "wwwroot/css/tuieditor/toastui-editor.min.css" ] }, 

讯享网

项目中很可能还有其他的样式文件,一起合并就好了,引用的时候我们只需要一个样式文件,这里就是 bulmarazor.min.css。

脚本部分

tuieditor 的 JavaScript 文件只有一个,当然一般 JavaScript 组件的脚本文件都是一个,如果是普通的 web 开发的话直接引入就可以了,但是在 Blazor 中有些麻烦,需要使用 JavaScript 互操作,互操作是指 C# 代码可调用到 JavaScript 代码,而 JavaScript 代码也可调用到 C# 代码。

C# 调用 JavaScript 代码有两种方法,一种是使用 IJSRuntime 调用挂载到 window 对象上的方法,另一种是使用模块隔离的方式调用,这里我们需要模块隔离,因为有以下优点:

  • 导入的 JavaScript 不再污染全局命名空间。
  • 库和组件的使用者不需要引用相关的 JavaScript。

关于 JavaScript 模块,可以参考这里 这里 ,使用 JavaScript 模块依赖于 import 和 export,而一般的 JavaScript 类库并不支持,所以我们需要些一些导出的代码,文件结构如下:

我们忽视红色标注,先来看一下 toastui-editor-export.js 这个文件:

讯享网export function initEditor(options) { options.el = document.getElementById(options.elid); let editor = new toastui.Editor.factory(options); return editor; } 

toastui-editor-all.min.JavaScript 这个文件就是 JavaScript 组件文件,我们不用去改它,也不应该去改它,因为后续升级了我们可以直接覆盖的,toastui-editor-export.js 就是我们专门写的一个导出类库中所需功能的导出文件。为了引用方便我们还是需要合并一下,就是图片示现的那样,合并配置如下:

 { "outputFileName": "wwwroot/js/tuieditor.min.js", "inputFiles": [ "wwwroot/jsplugin/tuieditor/toastui-editor-all.min.js", "wwwroot/jsplugin/tuieditor/toastui-editor-export.js" ] } 

现在我们使用隔离的方式引用 wwwroot/js/tuieditor.min.js 就可以了。当我们新建一个Razor组件项目的时候,会带有调用的例子,我们比猫画虎搞定:

讯享网using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; namespace BulmaRazor.Components { public class BulmaRazorJsInterop : IAsyncDisposable { private readonly Lazy<Task<IJSObjectReference>> tuiEditorModuleTask; public BulmaRazorJsInterop(IJSRuntime jsRuntime) { tuiEditorModuleTask = new(() => jsRuntime.InvokeAsync<IJSObjectReference>( "import", "./_content/BulmaRazor/js/tuieditor.min.js").AsTask()); } public async ValueTask<IJSObjectReference> TuiEditorInit(TuiEditorOptions options) { var module = await tuiEditorModuleTask.Value; return await module.InvokeAsync<IJSObjectReference>("initEditor", options.ToParams()); } public async ValueTask DisposeAsync() { if (tuiEditorModuleTask.IsValueCreated) { var module = await tuiEditorModuleTask.Value; await module.DisposeAsync(); } } } } 

Blazor 组件部分

组件文件是 TuiEditor.razor,UI代码是非常简单的,就是一个带有 id 属性的 div 容器,id 很重要,是我们互操作的基础,这里我们使用GUID生成唯一的id。
我们需要在 blazor 组件呈现之后调用 JavaScript 代码来初始化我们的 JavaScript 组件,调用 JavaScript 代码之后返回了js 对象的引用editor,注意editor和上述var module = await tuiEditorModuleTask.Value; 中的 module 是一样的,都是 JavaScript 对象引用。大致的代码如下:

@inject BulmaRazorJsInterop JsInterop <div id="@Id"></div> @code { readonly string Id = "tuiEditor_" + Guid.NewGuid().ToString("N"); IJSObjectReference editor; [Parameter] public TuiEditorOptions Options { get; set; } protected override void OnInitialized() { if (Options == null) Options = new TuiEditorOptions(); Options.elid = Id; base.OnInitialized(); } protected override async Task OnAfterRenderAsync(bool firstRender) { await base.OnAfterRenderAsync(firstRender); editor = await JsInterop.TuiEditorInit(Options); } } 

Options选项部分

讯享网 internal class JsParams:Dictionary<string,object> { public void AddNotNull(string key, object value) { if (value != null) { base.Add(key,value); } } } 

TuiEditorOptions 类除了参数之外,包含一个 ToParams() 的方法把自己转换成 JsParams:

 public class TuiEditorOptions { internal string elid { get; set; } /// <summary> /// Editor's height style value. Height is applied as border-box ex) '300px', '100%', 'auto' /// </summary> public string Height { get; set; } /// <summary> /// 是否是查看器 /// </summary> public bool? Viewer { get; set; } //...其他参数 internal JsParams ToParams() { JsParams ps = new JsParams(); var def = BulmaRazorOptions.DefaultOptions.TuiEditorOptions; ps.AddNotNull("elid", elid); ps.AddNotNull("viewer",Viewer); ps.AddNotNull("height", Height ?? def.Height); //...其他参数 return ps; } } 

有几个原因使用 JsParams :

  • null值可以不传递,因为js的options一般都用默认值,减少传输;
  • 可以使用默认设置,如上有个BulmaRazorOptions.DefaultOptions.TuiEditorOptions;

  • 可以灵活的手动处理参数,上面例子没有提现出来,不过组件写多了肯定会遇到这种情况;

对象的方法

JavaScript 组件一般也会公开许多实例方法,比如获得焦点,设置内容,获取内容等等,在在前面我们一直保存了 JavaScript 组件实例的引用,也就是在 TuiEditor 中的 editor 对象,向公开哪些方法在 TuiEditor.razor 中添加就是了:

讯享网 public void Focus() { editor?.InvokeVoidAsync("focus"); } public ValueTask<string> GetMarkdown() { return editor?.InvokeAsync<string>("getMarkdown") ?? new ValueTask<string>(""); } public void InsertText(string text) { editor?.InvokeVoidAsync("insertText", text); } public ValueTask<bool> IsViewer() { return editor?.InvokeAsync<bool>("isViewer") ?? new ValueTask<bool>(false); } //...其他需要的方法 

对象事件

 public static class JSCallbackManager { private static ConcurrentDictionary<string, Dictionary<string, Delegate>> eventHandlerDict = new(); public static void AddEventHandler(string objId, string eventKey, Delegate @delegate) { var eventHandlerList = eventHandlerDict.GetOrAdd(objId, (key) => new Dictionary<string, Delegate>()); eventHandlerList[eventKey]= @delegate; } public static void DisposeObject(string objId) { if (eventHandlerDict.Remove(objId, out Dictionary<string, Delegate> handlers)) { handlers.Clear(); } } [JSInvokable] public static object JSCallback(string objId, string eventKey) { if (eventHandlerDict.TryGetValue(objId, out Dictionary<string, Delegate> handlers)) { if (handlers.TryGetValue(eventKey, out Delegate d)) { var obj = d.DynamicInvoke(); return obj; } } return null; } } 
讯享网export function initEditor(options) { options.el = document.getElementById(options.elid); options.events = { load: function () { DotNet.invokeMethodAsync("BulmaRazor", "JSCallback", options.elid, "load"); } } let editor = new toastui.Editor.factory(options); return editor; } 

JavaScript 的事件还是需要用 js来做,然后在js方法内部调用 C# 方法。第二个是需要在 TuiEditor 中添加回调委托:

 [Parameter] public EventCallback<TuiEditor> OnLoad { get; set; } protected override void OnInitialized() { if (Options == null) Options = new TuiEditorOptions(); Options.elid = Id; //这里添加回调委托,并把js事件公开成了Blazor组件事件 JSCallbackManager.AddEventHandler(Id, "load", new Func<Task>(() => OnLoad.InvokeAsync(this))); base.OnInitialized(); } protected override ValueTask DisposeAsync(bool disposing) { //移除对象的所有回调委托 JSCallbackManager.DisposeObject(Id); return base.DisposeAsync(disposing); } 

这样我们就把 JavaScript 组件事件移植到了 Blazor 组件。

修整

经过上述不知,组件基本移植完了,但还不能很好的使用,第一,因为界面是 js在操作,所以我们应该禁用 Blazor组件的渲染:

讯享网 protected override bool ShouldRender() { return false; } 

在js的options中有个initialValue属性,是初始化内容的,我们改成Blazor的形式,最好是可以绑定:

 [Parameter] public EventCallback<TuiEditor> OnBlur { get; set; } protected override void OnInitialized() { if (Options == null) Options = new TuiEditorOptions(); Options.InitialValue = _value; Options.elid = Id; //这里也是通过js事件触发 JSCallbackManager.AddEventHandler(Id, "blur", new Func<Task>(async () => { await setValue(); await OnBlur.InvokeAsync(this); })); base.OnInitialized(); } private string _value; [Parameter] public string Value { get { return _value; } set { _value = value; SetMarkdown(value, true); } } [Parameter] public EventCallback<string> ValueChanged { get; set; } private async Task setValue() { _value = await GetMarkdown(); await ValueChanged.InvokeAsync(_value); } public void SetMarkdown(string markdown, bool cursorToEnd = true) { editor?.InvokeVoidAsync("setMarkdown", markdown, cursorToEnd); } 

这样我们就可以使用 Blazor 绑定语法了:

讯享网<TuiEditor @bind-Value="markdown"></TuiEditor> @code{ private string markdown = "# Init Title"; } 

效果如下:

在线效果点击这里

源代码

  • BulmaRazor官网
  • Gitee地址
  • Github地址

希望喜欢 Blazor 和 BulmaRazor 的朋友给个Star鼓励一下!该项目从2021年的春节假期开始,一个人做真心的累和耗时,您的鼓励是我坚持下去的最大动力!

小讯
上一篇 2025-01-12 07:39
下一篇 2025-04-07 11:14

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/126182.html