React In Blazor
在 Blaozr 執行環境內執行 React 元件。到現在為止 React 在前端UI仍是王者。假如可以在 Blazor 跑 React 元件那就能做更多事了。
引言
官方還不支援。基本上 Blazor 與 React 是競爭對手互不合作是合理的。基本上 Blazor 與 React 是不能直接互通的。然其共通的基底都是 JavaScript/ES6+ 所以還是有可能間接互通合作的。Blazor 的 IJSRuntime
可以與 JS 互通。React 的 ReactDOM 模組可以 render()
到 html element。經測試它們可以有限的互通,交互作用越多鷹架碼就越多。
相關文章
WHY
在 Blazor Server/WASM App 的現實世界可以使用的前端 UI 函式庫仍是很有限。而 React 前端件元件支援度仍是王者。假如可以在 Blazor 跑 React 元件那就能做更多事了。
比如:React Select --- 下拉元件
比如:react-quill / quill.js --- Rich Editor
比如:VenoBox 2 --- Image Gallery
比如:beautiful-react-diagrams --- 流程圖
比如:Chart.js、vis.js、D3 --- 數據視覺化
比如:QR Scanner --- 硬體控制
比如:PDF.js --- 在網頁上更好的報表操作
大體上 react 能用的在 Blaozr 也都能用。
HOW
基本上 Blazor 與 React 是無法互通的。不過都能與 JS 互通有無。
React.v18+
node.js / npm
Webpack.v5
Blazor JS interop
另一種方式:web-components
與 JS 互通有無的標準答案,其實就是 HTML 的自訂延伸元件解決方案之一,其基底是 custom elements 與 Custom Evnet 。可以用 html 版的 ActiveX/COM 元件來理解。只要是 html5 以上就能用當然也包括 WebForm。
理論上可以與所有 html 前端開發語言互通;現實上很骨感:可以 props-down 但 events-up 卻限制重重。到現在(2023/10) 為止,它仍只是『第三方學術等級』的標準方案成熟度不足以通用。
就算是 react 也不能與 web-component 直接互通有無。相信用 react 開發的人應該根本不想用它。與 Blazor 的溝通難度更高。雖然如此:本人對『web-components 2』是很期待的。
Blazor 與 React 溝通的四種模式
因為 Blazor 與 React 不能直接互通有無,故只好繞點路。
一、單向 props-down
顯示展示資訊,如:大數據的圖表。 Graph 相關的資源 React 遠比 Blazor 多很多。
二、雙向 props-down + events-up
當然也會有向上層通訊的需求。
三、Re-Mount
元件刷新重整。法一就是直接重新 render 元件,缺點是元件的 state 也會重置不見。 實務上,這還好可以補。因為 React 在此應用情境上只是輔助不是畫面的主體。
四、DidUpdate (進階用法)
元件刷新重整。法二是局部刷新元件,這樣元件的 state 可以維持。 實作很麻煩,效益還好。因為 React render 的速度很快。 全部刷新跟局部刷新元件在體感上是一樣快的。 實務上,非得局部刷新元件的應用還沒想到。因為 React 在此應用情境上只是輔助不是畫面的主體。
開發環境
平台: .NET6 框架:Blazor WASM/Server App IDE:Visual Studio 2022 React:React v18.2 + webpack bundle。
在 Blazor 專案內再組織 React 開發環境
一言難盡的程序就自己加油吧。
參考二:React 18 with Webpack 5 — Project setup steps
組織 React 元件開發環境之 NPM 指令紀錄
npm init -y
npm install webpack webpack-cli --save-dev
npm install -D react react-dom
npm install -D html-webpack-plugin
npm install -D style-loader css-loader
npm install -D babel-loader @babel/core @babel/preset-env @babel/preset-react
手動加入 webpack.config.js
。等等。
用 npm run build
指令 bundle React 程式碼。
程式碼說明-單向 props-down
可應用顯示展示資訊,如:Graph圖表。
React 前端
import React from 'react'
export default function MyTitle({ title }) {
return (
<div className="p-2 my-2" style={{ border: 'solid 2px red', borderRadius: 8}}>
<h3>{title}</h3>
<h4>我用 React 開發出來的</h4>
</div>
)
}
類註冊
基本上 Blazor 與 React 是不能直接互通的不過可以間接互通。方法就是類似註冊的行為。
import React from 'react'
import ReactDOM from 'react-dom/client'
import MyTitle from './MyTitle'
//## 註冊單向繫結 React 元件:MyTitle
window.renderMyTitle = function (rootElement, title) {
const root = ReactDOM.createRoot(rootElement);
root.render(<MyTitle title={title} />);
}
Blazor 前端 - 直接 render React 元件
@page "/"
@inject IJSRuntime jsr
<div @ref=refTitleElement></div>
@* for React to render *@
@code {
ElementReference refTitleElement;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender) {
await jsr.InvokeVoidAsync("renderMyTitle", refTitleElement, "我是抬頭");
}
}
}
程式碼說明-雙向 props-donw + events-up 與 Re-Mount
import React, { useState } from 'react'
export default function MyCounter({ initCount /* int */, onChange /* event */ }) {
const [count, setCount] = useState(initCount || 0);
function handleClick() {
const newCount = count + 1;
setCount(newCount)
onChange(newCount)
}
return (
<div className="p-2 my-2" style={{ border: 'solid 2px red', borderRadius: 8 }}>
<h3>我用 React 開發出來的</h3>
<p>You clicked {count} times</p>
<button className="btn btn-primary" onClick={handleClick}>
Click me
</button>
</div>
)
}
類註冊
//## 註冊雙向繫結 React 元件:MyCounter
window.renderMyCounter = function (dotNetObject, rootElement, initCount) {
function handleChange(newCount) {
// events up
dotNetObject.invokeMethodAsync('OnCountChange', newCount);
console.log(`你變了 => ${newCount}`);
}
// props down
const root = ReactDOM.createRoot(rootElement);
root.render(<MyCounter initCount={initCount} onChange={handleChange} />);
}
Blazor 前端 - 再包裝成 Blazor 元件才使用
@inject IJSRuntime jsr
@inject ILogger<MyCounter> logger
<div @ref=refRoot></div>
@* for React to render *@
@code {
[Parameter] public int InitCount { get; set; } = 0;
[Parameter] public EventCallback<int> OnChange { get; set; }
ElementReference refRoot;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
if(firstRender)
{
await jsr.InvokeVoidAsync("renderMyCounter", DotNetObjectReference.Create(this), refRoot, InitCount);
await OnChange.InvokeAsync(InitCount);
}
}
/// 將用於 Re-Mount,重置元件
public async Task ResetAsync()
{
logger.LogInformation("ON:Reset");
await jsr.InvokeVoidAsync("renderMyCounter", DotNetObjectReference.Create(this), refRoot, InitCount);
await OnChange.InvokeAsync(InitCount);
}
/// <summary>
/// 當 React 元件有訊息送上來時觸發。
/// </summary>
[JSInvokable]
public Task OnCountChange(int newCount) => OnChange.InvokeAsync(newCount);
}
Blazor 前端 - 應用
@page "/"
<div class='p-2 my-2' style='border:solid 2px blue; border-radius: 8px'>
<MyCounter @ref=refMyCounter
InitCount=123
OnChange="(newCount)=> count = newCount" />
<h4 class="py-3">這裡不在 React 元件裡面 count: @count</h4>
<button class="btn btn-secondary" @onclick=HandleResetCount>重置 MyCounter</button>
</div>
@code {
MyCounter refMyCounter = default!; //將用於 Re-Mount
int count = 0;
async Task HandleResetCount()
{
// 重置計數器 (Re-Mount)
await refMyCounter.ResetAsync();
}
}
完整程式碼
沒圖沒真象
應用之一:利用 React 加掛 chart.js
模組顯示圖表。


(EOF)
Last updated