小编典典

Shadow DOM中的React Component时未触发Click事件

reactjs

我有一个特殊的情况,我需要用Web组件封装一个React组件。设置似乎非常简单。这是React代码:

// React Component
class Box extends React.Component {
  handleClick() {
    alert("Click Works");
  }
  render() {
    return (
      <div 
        style={{background:'red', margin: 10, width: 200, cursor: 'pointer'}} 
        onClick={e => this.handleClick(e)}>

        {this.props.label} <br /> CLICK ME

      </div>
    );
  }
};

// Render React directly
ReactDOM.render(
  <Box label="React Direct" />,
  document.getElementById('mountReact')
);

HTML:

<div id="mountReact"></div>

这可以很好地安装并且click事件起作用。现在,当我围绕React Component创建一个Web
Component包装器时,它可以正确呈现,但是click事件不起作用。这是我的Web组件包装器:

// Web Component Wrapper
class BoxWebComponentWrapper extends HTMLElement {
  createdCallback() {
    this.el      = this.createShadowRoot();
    this.mountEl = document.createElement('div');
    this.el.appendChild(this.mountEl);

    document.onreadystatechange = () => {
      if (document.readyState === "complete") {
        ReactDOM.render(
          <Box label="Web Comp" />,
          this.mountEl
        );
      }
    };
  }
}

// Register Web Component
document.registerElement('box-webcomp', {
  prototype: BoxWebComponentWrapper.prototype
});

这是HTML:

<box-webcomp></box-webcomp>

有什么我想念的吗?还是React拒绝在Web组件内部工作?我见过像Maple.JS这样的库,它可以完成这种工作,但是它们的库可以工作。我觉得我错过了一件小事。

这是CodePen,因此您可以看到问题:

http://codepen.io/homeslicesolutions/pen/jrrpLP


阅读 334

收藏
2020-07-22

共1个答案

小编典典

事实证明,Shadow DOM重新定位单击事件并将事件封装在阴影中。React不喜欢这样做,因为它们本身不支持Shadow
DOM,因此事件委托已关闭,事件也未触发。

我决定要做的是将事件重新绑定到实际的影子容器,该影子容器在技术上是“光明的”。我使用跟踪事件的冒泡,event.path并在上下文中将所有React事件处理程序触发到影子容器。

我添加了一个’retargetEvents’方法,该方法将所有可能的事件类型绑定到容器。然后,它将通过找到“
__reactInternalInstances”来调度正确的React事件,并在事件范围/路径内查找相应的事件处理程序。

retargetEvents() {
    let events = ["onClick", "onContextMenu", "onDoubleClick", "onDrag", "onDragEnd", 
      "onDragEnter", "onDragExit", "onDragLeave", "onDragOver", "onDragStart", "onDrop", 
      "onMouseDown", "onMouseEnter", "onMouseLeave","onMouseMove", "onMouseOut", 
      "onMouseOver", "onMouseUp"];

    function dispatchEvent(event, eventType, itemProps) {
      if (itemProps[eventType]) {
        itemProps[eventType](event);
      } else if (itemProps.children && itemProps.children.forEach) {
        itemProps.children.forEach(child => {
          child.props && dispatchEvent(event, eventType, child.props);
        })
      }
    }

    // Compatible with v0.14 & 15
    function findReactInternal(item) {
      let instance;
      for (let key in item) {
        if (item.hasOwnProperty(key) && ~key.indexOf('_reactInternal')) {
          instance = item[key];
          break;
        } 
      }
      return instance;
    }

    events.forEach(eventType => {
      let transformedEventType = eventType.replace(/^on/, '').toLowerCase();

      this.el.addEventListener(transformedEventType, event => {
        for (let i in event.path) {
          let item = event.path[i];

          let internalComponent = findReactInternal(item);
          if (internalComponent
              && internalComponent._currentElement 
              && internalComponent._currentElement.props
          ) {
            dispatchEvent(event, eventType, internalComponent._currentElement.props);
          }

          if (item == this.el) break;
        }

      });
    });
  }

当我将React组件渲染到阴影DOM中时,我将执行“ retargetEvents”

createdCallback() {
    this.el      = this.createShadowRoot();
    this.mountEl = document.createElement('div');
    this.el.appendChild(this.mountEl);

    document.onreadystatechange = () => {
      if (document.readyState === "complete") {

        ReactDOM.render(
          <Box label="Web Comp" />,
          this.mountEl
        );

        this.retargetEvents();
      }
    };
  }

我希望这适用于React的未来版本。这是它的代码笔:

http://codepen.io/homeslicesolutions/pen/ZOpbWb

感谢@mrlew提供的链接为我提供了解决方法的线索,也感谢@Wildhoney考虑与我相同的波长=)。

2020-07-22