如何在 React 组件中使用 dojo 小部件?

How to use a dojo widget inside a react component?

有什么方法可以在 React 组件中重用来自其他库的 component/widget 吗? 例如

export default function App() {
 const [count, setCount] = useState(0);
 return (
  <button onClick={() => setCount(count + 1)}>{count}</button>
  <button id="btn" onClick={() => function(){
    require(["dijit/form/Button", "dojo/domReady!"], function(Button) {
      var button = new Button({
          label: "Click Me!",
          onClick: function(){ console.log("Opening a dojo dialog"); }
      }, "btn");
      button.startup();
  });
  }}>Open Dojo component</button>

  <br />
</div>
);

在这里,这是一个基本的反应组件。我正在尝试在其中使用 dojo 按钮,但显然不起作用。我实际上在考虑在反应中有一个按钮事件并调用一些解析 dojo 小部件的 javascript 函数。不确定这是否有效。 有没有更好的方法?

您的尝试失败的原因是您仍在 React JSX 中呈现普通按钮,当您单击该按钮时,您正在创建一个新的 Dojo 按钮,但您没有将它添加到DOM。即使你确实将它添加到 DOM,React 可能仍会在稍后重新渲染时将其删除,因为它不是 JSX 的一部分。

我们最近将React集成到现有的Dojo应用中,我们将Dojo widgets放入React组件的方法如下。

高级概述是我们创建了特殊的 React 组件,这些组件在其中“托管”Dojo 小部件。我会从头开始,一路往下。

这些托管的 Dojo 小部件的用法大多是直接的,看起来类似于以下 JSX

<div>
  <Button
    dojoProps={{ label: "Add" }}
    dojoEvents={{ click: onAdd }}
  />
  <Button
    dojoProps={{ label: "Remove" }}
    dojoEvents={{ click: onRemove }}
  />
</div>

其中 Button 是我们的 Dojo 托管组件。 dojoProps 属性 中的条目被传递到小部件的构造函数中,并在属性更改时传递到 .set(...) 中。然后 dojoEvents 中的条目在创建时以及更改时也会传递到 .on(...) 中。

Buttonclass是这样的(在TS里,但是应该很容易翻译成JS)

import * as DojoButton from "dijit/form/Button";
import { DojoInReactComponent } from "../DojoInReactComponent";

export class Button extends DojoInReactComponent<DojoButton> {
  constructor(props: Button["props"]) {
    super(new DojoButton(props.dojoProps), props);
  }
}

我们为每个希望在 React 中包装和显示的 Dojo 小部件制作了这些 classes 之一,并且可以在整个项目中重复使用。请注意,这是创建小部件并将道具传递到小部件的构造函数的地方。

实现的重要部分在DojoInReactComponent class:

import * as React from "react";
import * as _WidgetBase from "dijit/_WidgetBase";

/**
 * A React component that hosts a Dojo widget
 */
export abstract class DojoInReactComponent
    <W extends _WidgetBase, P extends DojoInReactComponentProps<W> = DojoInReactComponentProps<W>>
    extends React.Component<P> {

    /** Stores a React Ref to the actual DOMNode that we place the widget at */
    private readonly widgetRef: React.RefObject<HTMLSpanElement>;
    /** Cache of the registered event handles for this widget (used to cleanup handles on unmount) */
    private readonly eventsRegistered: EventRegistrationCache<W> = {};
    /** The actual widget that will be stored in this component */
    readonly widget: W;

    constructor(widget: W, props: P) {
        super(props);
        this.widgetRef = React.createRef();
        this.widget = widget;
    }

    componentDidMount() {
        if (!this.widgetRef.current) {
            throw new Error("this.widgetRef was not set");
        }

        // First, set properties
        this.widget.set(this.props.dojoProps ?? {});

        // Then set event handlers. This is the first time it happens so
        // the eventsRegistered cache is empty (ie: nothing to remove).
        this.addEventHandlers(this.props.dojoEvents);

        // Finally, place it at the domNode that this component created when it rendered.
        this.widget.placeAt(this.widgetRef.current);
    }

    componentDidUpdate(prevProps: P) {
        // First, update props. Note that we merge the old and new properties together here before setting to
        // ensure nothing drastically changes if we don't pass in a property. If we want it to change, we need to
        // explicitly pass it in to the dojoProps property in the TSX.
        // This also attempts to make it obvious that not setting a property in the TSX will leave it unchanged,
        // compared to it's existing value, as that's the default behaviour of Dojo widgets and .set().
        const props = { ...prevProps.dojoProps ?? {}, ...this.props.dojoProps ?? {} };
        this.widget.set(props);

        // Then update event handlers. Note that we don't do this in a "smart" way, but instead we just remove all
        // existing handlers, and then re-add the supplied ones. Generally it will mean removing and re-adding the same
        // handlers, but it's much easier than trying to diff them.
        this.removeAllEventHandlers();
        this.addEventHandlers(this.props.dojoEvents);
    }

    componentWillUnmount() {
        // On cleanup we need to remove our handlers that we set up to ensure no memory leaks.
        this.removeAllEventHandlers();

        // Finally we destroy the widget
        this.widget.destroyRecursive();
    }

    private addEventHandlers(events: EventsType<W> | undefined) {
        if (!events) {
            return;
        }

        for (const key of keysOf(events)) {
            const newHandle = this.widget.on(key as any, events[key] as any);
            this.eventsRegistered[key] = newHandle;
        }
    }

    private removeAllEventHandlers() {
        for (const key of keysOf(this.eventsRegistered)) {
            const handle = this.eventsRegistered[key];
            if (handle) {
                handle.remove();
            }

            delete this.eventsRegistered[key];
        }
    }

    render() {
        return <span ref={this.widgetRef}></span>;
    }
}

function keysOf<T extends {}>(obj: T) {
    return Object.keys(obj) as Array<keyof T>;
}

type EventsType<T extends _WidgetBase> = Record<string, () => void>;
type PropsType<T extends _WidgetBase> = Partial<T>;

export interface DojoInReactComponentProps<W extends _WidgetBase> {
    dojoProps?: PropsType<W>;
    dojoEvents?: EventsType<W>;
}

type EventRegistrationCache<W extends _WidgetBase> = Record<string, dojo.Handle>;

这里有一点要解包,但它的一般要点是:

  • render() 生成一个简单的 <span> 元素,我们保持对它的引用以将我们的 Dojo 小部件挂载到
  • componentDidMount() 将 Dojo 小部件的属性和事件处理程序更新为我们提供的属性。然后它将实际的 Dojo 小部件放入由 React 组件
  • 生成的 DOM
  • componentDidUpdate() 如果属性和事件处理程序因 React 组件的重新渲染而发生更改,则再次更新它们
  • componentWillUnmount() 在 React 组件被删除时被调用,并通过调用 .destroyRecursive() 将托管的 Dojo 小部件从 DOM
  • 中删除

底部的类型只是帮助 TS 为我们推断一切的实用类型,但是 EventsTypeEventsRegistrationCache 使用了额外的更改,这些更改太长而无法在此处写出,所以我'我已经用存根替换了它们(但如果你只是使用 JS,那没关系)。

如果您对此有任何疑问,请告诉我。