React In Blazor - DidUpdate

引言

Blazor 與 React 本身不互支援,尚無法直接通訊。故導入 Mediator pattern 找到了JavScript 版本的 mediator-js 為通訊中心。(註:之後找到一套 Event Bus: js-event-bus 用較新的觀念重構 Mediator。)

相關文章

React In Blazor

React + r2wc in Blazor

源碼紀錄

導入 mediator-js 模組

導入 mediator-js 模組,版號為 mediator-js@0.11.0。以 Browser global 模式使用。

其溝通的通道名為『channel』格式為 string。

index.html
<script src="/js/Mediator.min.js"></script>
<script>
  //※ 導入 mediator 以實現 Blazor 與 React 進階通訊。
  window.__mediator = window.Mediator();
</script>

※ 補充 on 2023-10-04

應用:包裝 react-select 成 Blazor 元件使用

包裝後名稱為 MySelect2,包裝後應用的碼大概如下:

Pages\RenderReactCom.razor
@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

React\src\index.js
...略...
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 通訊。

React\src\MySelectWrapper.js
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