React In Blazor - DidUpdate
引言
Blazor 與 React 本身不互支援,尚無法直接通訊。故導入 Mediator pattern 找到了JavScript 版本的 mediator-js 為通訊中心。(註:之後找到一套 Event Bus: js-event-bus 用較新的觀念重構 Mediator。)
相關文章
源碼紀錄
導入 mediator-js 模組
導入 mediator-js 模組,版號為 mediator-js@0.11.0
。以 Browser global 模式使用。
其溝通的通道名為『channel』格式為 string。
<script src="/js/Mediator.min.js"></script>
<script>
//※ 導入 mediator 以實現 Blazor 與 React 進階通訊。
window.__mediator = window.Mediator();
</script>
※ 補充 on 2023-10-04
發現JS也有人實作 Event Bus 機制,名為 js-event-bus 用較新的觀念重構可以取代 mediator-js 。Event Bus 其實也是 mediator pattern 的實作方案之一。
應用:包裝 react-select 成 Blazor 元件使用
包裝後名稱為 MySelect2,包裝後應用的碼大概如下:
@page "/render-react-component"
...略...
<MySelect2 Options=@optionList Value=@selectedOption OnChange=@HandleSelectChange />
@code {
ValueLabel? selectedOption;
List<ValueLabel> optionList = new()
{
new("美式咖啡","美式咖啡"),
new("卡布奇諾","卡布奇諾"),
new("摩卡","摩卡"),
};
async Task HandleSelectChange(ValueLabel selectedOption)
{
this.selectedOption = selectedOption;
await Task.CompletedTask;
}
}
Blazor 元件引入 react 元件
其中讓 elementId 就是 channel-id 剛好可以省下一些碼。
@using System.Dynamic;
@inject IJSRuntime jsr
<div id=@elementId></div>
@code {
[Parameter, EditorRequired] public List<ValueLabel> Options { get; set; } = default!;
[Parameter] public ValueLabel? Value { get; set; }
[Parameter] public EventCallback<ValueLabel> OnChange { get; set; }
readonly string elementId = $"MySelect2-{Guid.NewGuid():D}";
List<ValueLabel> _oldOptions = default!;
bool f_dirty = false;
dynamic newAttrs = default!;
protected override void OnParametersSet()
{
base.OnParametersSet();
newAttrs = new ExpandoObject();
if (_oldOptions != Options)
{
newAttrs.options = Options;
_oldOptions = Options;
}
newAttrs.value = Value;
f_dirty = true;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
if (firstRender)
{
await jsr.InvokeAsync<string>("renderMySelect2", DotNetObjectReference.Create(this), elementId, Options, Value);
//await InvokeAsync(StateHasChanged);
}
else if (f_dirty)
{
//※ 使用 mediator 間接通訊 - update props --- 要求 DidUpate
await jsr.InvokeVoidAsync("window.__mediator.publish", elementId, (object)newAttrs);
//叫用JS: window.__mediator.publish(channel, payload);
f_dirty = false;
}
}
/// <summary>
/// 當 React 元件有訊息送上來時觸發。
/// </summary>
[JSInvokable]
public Task OnSelect(ValueLabel selectedOption) => OnChange.InvokeAsync(selectedOption);
}
類註冊 renderMySelect2
...略...
window.renderMySelect2 = function (dotNetObject, elementId, options, value) {
//注意:此例 elementId 也是 channelId。將透過 mediator 間接通訊。
const rootElement = document.getElementById(elementId)
const root = ReactDOM.createRoot(rootElement)
root.render(
<React.StrictMode>
<MySelectWrapper dotNetObject={dotNetObject} channel={elementId} options={options} value={value} />
</React.StrictMode>
);
}
以 mediator 間接通訊,所以少不了再包裝一層
因為不能直接 DidUpdate ,再包一層用 useState 緩存元件 props 再喂入目標元件。
把目標元件 react-select 包裝以間接用 mediator 實現 DidUpdate 通訊。
import React, { useState, useEffect } from 'react'
import Select from 'react-select'
export default function MySelectWrapper({ dotNetObject, channel, options, value }) {
const [_options, setOptions] = useState(options);
const [_value, setValue] = useState(value);
useEffect(() => {
// 註冊通訊
window.__mediator.subscribe(channel, (payload) => {
//## update props --- 實現 DidUpate
const { value: newValue, options: newOptions } = payload
if (!!newValue) setValue(newValue)
if (!!newOptions) setOptions(newOptions)
});
return () => {
// 解除註冊通訊
window.__mediator.remove(channel)
}
}, [])
//## events up with dotNetObject
function handleChange(selectedOption /* LabelValue */) {
dotNetObject.invokeMethodAsync('OnSelect', selectedOption);
}
return (
// 這是 react-select 元件
<Select options={_options} onChange={handleChange} value={_value} />
)
}
參考
(EOF)
Last updated