javascript
如何优雅的移植JavaScript组件到Blazor
Blazor作為一個(gè)新興的交互式 Web UI 的框架,有其自身的優(yōu)缺點(diǎn),如果現(xiàn)有的 JavaScript 組件能移植到 Blazor,無疑讓 Blazor 如虎添翼,本文就介紹一下自己在開發(fā) BulmaRazor 組件庫的時(shí),封裝現(xiàn)有的 JavaScript 組件的方法,文中以 TuiEditor 為例。
開始
首先找到現(xiàn)有 TuiEditor 的主頁或者文檔,這一步很簡單,我們找到官網(wǎng) https://ui.toast.com/tui-editor/?,分析一下組件的使用方法,一般都是有樣式文件,有 JavaScript 文件,有一個(gè) options 對象來初始化一個(gè)主對象,主對象上有方法和事件,大概就是這些了,我們先下載所需的文件,然后一步一步處理。
樣式部分
該組件需要兩個(gè)樣式 codemirror.min.css 和 toastui-editor.min.css ,由于一個(gè)組件庫不只這一個(gè)組件,為了引用方便,我們需要使用 BuildBundlerMinifier 合并文件,不知道 BuildBundlerMinifier 的同學(xué)網(wǎng)上查一下。
在網(wǎng)站的根目錄需要有 BuildBundlerMinifier 所需的配置文件 bundleconfig.json,對應(yīng)的配置如下 :
項(xiàng)目中很可能還有其他的樣式文件,一起合并就好了,引用的時(shí)候我們只需要一個(gè)樣式文件,這里就是 bulmarazor.min.css。
腳本部分
tuieditor 的 JavaScript 文件只有一個(gè),當(dāng)然一般 JavaScript 組件的腳本文件都是一個(gè),如果是普通的 web 開發(fā)的話直接引入就可以了,但是在 Blazor 中有些麻煩,需要使用 JavaScript 互操作,互操作是指 C# 代碼可調(diào)用到 JavaScript 代碼,而 JavaScript 代碼也可調(diào)用到 C# 代碼。
C# 調(diào)用 JavaScript 代碼有兩種方法,一種是使用 IJSRuntime 調(diào)用掛載到 window 對象上的方法,另一種是使用模塊隔離的方式調(diào)用,這里我們需要模塊隔離,因?yàn)橛幸韵聝?yōu)點(diǎn):
導(dǎo)入的 JavaScript 不再污染全局命名空間。
庫和組件的使用者不需要引用相關(guān)的 JavaScript。
關(guān)于 JavaScript 模塊,可以參考這里?這里?,使用 JavaScript 模塊依賴于 import 和 export,而一般的 JavaScript 類庫并不支持,所以我們需要些一些導(dǎo)出的代碼,文件結(jié)構(gòu)如下:
我們忽視紅色標(biāo)注,先來看一下 toastui-editor-export.js 這個(gè)文件:
toastui-editor-all.min.JavaScript 這個(gè)文件就是 JavaScript 組件文件,我們不用去改它,也不應(yīng)該去改它,因?yàn)楹罄m(xù)升級了我們可以直接覆蓋的,toastui-editor-export.js 就是我們專門寫的一個(gè)導(dǎo)出類庫中所需功能的導(dǎo)出文件。為了引用方便我們還是需要合并一下,就是圖片示現(xiàn)的那樣,合并配置如下:
{"outputFileName": "wwwroot/js/tuieditor.min.js","inputFiles": ["wwwroot/jsplugin/tuieditor/toastui-editor-all.min.js","wwwroot/jsplugin/tuieditor/toastui-editor-export.js"]}現(xiàn)在我們使用隔離的方式引用 wwwroot/js/tuieditor.min.js 就可以了。當(dāng)我們新建一個(gè)Razor組件項(xiàng)目的時(shí)候,會(huì)帶有調(diào)用的例子,我們比貓畫虎搞定:
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代碼是非常簡單的,就是一個(gè)帶有 id 屬性的 div 容器,id 很重要,是我們互操作的基礎(chǔ),這里我們使用GUID生成唯一的id。
我們需要在 blazor 組件呈現(xiàn)之后調(diào)用 JavaScript 代碼來初始化我們的 JavaScript 組件,調(diào)用 JavaScript 代碼之后返回了js 對象的引用editor,注意editor和上述var module = await tuiEditorModuleTask.Value;?中的 module 是一樣的,都是 JavaScript 對象引用。大致的代碼如下:
Options選項(xiàng)部分
TuiEditor 組件中有個(gè)參數(shù) TuiEditorOptions ,是要對應(yīng) JavaScript 中的 options 參數(shù)的,我們需要自己定義一個(gè),這里我們使用兩個(gè)類來使用,一個(gè)是針對 JavaScript 的 JsParams 類似字典的對象,一個(gè)是針對使用者的 TuiEditorOptions 。
JsParams 就是一個(gè)Dictionary<string,object>,為了方便,我們過濾了空值:
TuiEditorOptions 類除了參數(shù)之外,包含一個(gè) ToParams() 的方法把自己轉(zhuǎn)換成 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; }//...其他參數(shù)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);//...其他參數(shù)return ps;} }有幾個(gè)原因使用 JsParams :
null值可以不傳遞,因?yàn)閖s的options一般都用默認(rèn)值,減少傳輸;
可以使用默認(rèn)設(shè)置,如上有個(gè)BulmaRazorOptions.DefaultOptions.TuiEditorOptions;
可以靈活的手動(dòng)處理參數(shù),上面例子沒有提現(xiàn)出來,不過組件寫多了肯定會(huì)遇到這種情況;
對象的方法
JavaScript 組件一般也會(huì)公開許多實(shí)例方法,比如獲得焦點(diǎn),設(shè)置內(nèi)容,獲取內(nèi)容等等,在在前面我們一直保存了 JavaScript 組件實(shí)例的引用,也就是在 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 中直接設(shè)置 JavaScript 函數(shù)就可以了,但是并不能把 C# 方法或者委托傳遞給 js,這里就需要用到 JavaScript 調(diào)用C#方法了。
Blazor 框架中 JavaScript 只能調(diào)用靜態(tài)方法,而我們實(shí)際中是基于對象來寫邏輯的,所有我專門寫了一個(gè)類來處理js的調(diào)用,JSCallbackManager:
我們使用一個(gè)嵌套的字典來保存了Blazor組件的回調(diào)委托,每一個(gè)組件對象都有一個(gè)唯一的Id,每一個(gè)組件類型都可以有不同名稱的 JavaScript 事件回調(diào)。
比如我們想訂閱 JavaScript 組件實(shí)例的 load 事件,我們需要改兩個(gè)地方,第一個(gè)是 toastui-editor-export.js 導(dǎo)出文件:
JavaScript 的事件還是需要用 js來做,然后在js方法內(nèi)部調(diào)用 C# 方法。第二個(gè)是需要在 TuiEditor 中添加回調(diào)委托:
[Parameter]public EventCallback<TuiEditor> OnLoad { get; set; }protected override void OnInitialized(){if (Options == null)Options = new TuiEditorOptions();Options.elid = Id;//這里添加回調(diào)委托,并把js事件公開成了Blazor組件事件JSCallbackManager.AddEventHandler(Id, "load", new Func<Task>(() => OnLoad.InvokeAsync(this)));base.OnInitialized();}protected override ValueTask DisposeAsync(bool disposing){//移除對象的所有回調(diào)委托JSCallbackManager.DisposeObject(Id);return base.DisposeAsync(disposing);}這樣我們就把 JavaScript 組件事件移植到了 Blazor 組件。
修整
經(jīng)過上述不知,組件基本移植完了,但還不能很好的使用,第一,因?yàn)榻缑媸?js在操作,所以我們應(yīng)該禁用 Blazor組件的渲染:
protected override bool ShouldRender(){return false;}在js的options中有個(gè)initialValue屬性,是初始化內(nèi)容的,我們改成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事件觸發(fā)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"; }效果如下:
在線效果點(diǎn)擊這里
源代碼
BulmaRazor官網(wǎng)
Gitee地址
Github地址
希望喜歡 Blazor 和 BulmaRazor 的朋友給個(gè)Star鼓勵(lì)一下!該項(xiàng)目從2021年的春節(jié)假期開始,一個(gè)人做真心的累和耗時(shí),您的鼓勵(lì)是我堅(jiān)持下去的最大動(dòng)力!
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的如何优雅的移植JavaScript组件到Blazor的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: asp.net core 自定义 Con
 - 下一篇: 微软的焦虑?想多了!从.NET6 Pre