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

TuiEditor 组件中有个参数 TuiEditorOptions ,是要对应 JavaScript 中的 options 参数的,我们需要自己定义一个,这里我们使用两个类来使用,一个是针对 JavaScript 的 JsParams 类似字典的对象,一个是针对使用者的 TuiEditorOptions 。
JsParams 就是一个Dictionary<string,object>,为了方便,我们过滤了空值:

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); } //...其他需要的方法 对象事件

JavaScript 组件对象有自己的事件,在 JavaScript 中直接设置 JavaScript 函数就可以了,但是并不能把 C# 方法或者委托传递给 js,这里就需要用到 JavaScript 调用C#方法了。
Blazor 框架中 JavaScript 只能调用静态方法,而我们实际中是基于对象来写逻辑的,所有我专门写了一个类来处理js的调用,JSCallbackManager:

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; } }

我们使用一个嵌套的字典来保存了Blazor组件的回调委托,每一个组件对象都有一个唯一的Id,每一个组件类型都可以有不同名称的 JavaScript 事件回调。
比如我们想订阅 JavaScript 组件实例的 load 事件,我们需要改两个地方,第一个是 toastui-editor-export.js 导出文件:

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 组件。

修整

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzyszp.html