Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/javascript/385.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Javascript 单击阴影DOM中的React组件时不触发的事件_Javascript_Reactjs_Web Component_Shadow Dom_Custom Element - Fatal编程技术网

Javascript 单击阴影DOM中的React组件时不触发的事件

Javascript 单击阴影DOM中的React组件时不触发的事件,javascript,reactjs,web-component,shadow-dom,custom-element,Javascript,Reactjs,Web Component,Shadow Dom,Custom Element,我有一个特例,需要用Web组件封装React组件。设置似乎非常简单。以下是反应代码: // React Component class Box extends React.Component { handleClick() { alert("Click Works"); } render() { return ( <div style={{background:'red', margin: 10, width: 200, curso

我有一个特例,需要用Web组件封装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')
);
//React组件
类框扩展了React.Component{
handleClick(){
警报(“点击工作”);
}
render(){
返回(
这个.handleClick(e)}>
{this.props.label}
单击我 ); } }; //渲染直接反应 ReactDOM.render( , document.getElementById('mountReact') );
HTML:


这会很好地装载,单击事件也会起作用。现在,当我围绕React组件创建一个Web组件包装器时,它可以正确地呈现,但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
});
//Web组件包装器
类BoxWebComponentRapper扩展HtmleElement{
createdCallback(){
this.el=this.createShadowRoot();
this.mountEl=document.createElement('div');
this.el.appendChild(this.mountEl);
document.onreadystatechange=()=>{
如果(document.readyState==“完成”){
ReactDOM.render(
,
这是蒙特尔
);
}
};
}
}
//注册Web组件
文件注册表项('box-webcomp'{
prototype:BoxWebComponentRapper.prototype
});
以下是HTML:

<box-webcomp></box-webcomp>

有什么我遗漏的吗?或者React是否拒绝在Web组件中工作?我见过一个像Maple.JS这样的库,它可以做这种事情,但是它们的库可以工作。我觉得我错过了一件小事

这是代码笔,您可以看到问题:


替换
this.el=this.createShadowRoot()带有
this.el=document.getElementById(“mountReact”)刚刚工作。可能是因为react有一个全局事件处理程序,而阴影dom意味着事件重定目标。

结果是阴影dom重定目标单击事件并将事件封装在阴影中。React不喜欢这样,因为它们本机不支持影子DOM,因此事件委派处于关闭状态,不会触发事件

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

我添加了一个“retargetEvents”方法,该方法将所有可能的事件类型绑定到容器。然后,它将通过查找“\uuuReactInternalInstances”来调度正确的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();
      }
    };
  }
createdCallback(){
this.el=this.createShadowRoot();
this.mountEl=document.createElement('div');
this.el.appendChild(this.mountEl);
document.onreadystatechange=()=>{
如果(document.readyState==“完成”){
ReactDOM.render(
,
这是蒙特尔
);
此参数为.retargetEvents();
}
};
}
我希望这对React的未来版本有效。这是它工作的代码笔:


感谢@mrlew的链接,它为我提供了解决这个问题的线索,也感谢@Wildhoney与我在相同的波长上思考

我修复了一个bug,清理了的代码。我在这里发布了一个npm包:

用法如下

安装

添加阴影dom重定目标事件

npm安装响应阴影dom重定目标事件--保存

使用

导入
retargetEvents
并在
shadowDom

import retargetEvents from 'react-shadow-dom-retarget-events';

class App extends React.Component {
  render() {
    return (
        <div onClick={() => alert('I have been clicked')}>Click me</div>
    );
  }
}

const proto = Object.create(HTMLElement.prototype, {
  attachedCallback: {
    value: function() {
      const mountPoint = document.createElement('span');
      const shadowRoot = this.createShadowRoot();
      shadowRoot.appendChild(mountPoint);
      ReactDOM.render(<App/>, mountPoint);
      retargetEvents(shadowRoot);
    }
  }
});
document.registerElement('my-custom-element', {prototype: proto});
从“react shadow dom重定目标事件”导入重定目标事件;
类应用程序扩展了React.Component{
render(){
返回(
警报(“我已被单击”)}>单击我
);
}
}
const proto=Object.create(HTMLElement.prototype{
附件回调:{
值:函数(){
常量mountPoint=document.createElement('span');
const shadowRoot=this.createShadowRoot();
shadowRoot.appendChild(挂载点);

ReactDOM.render(

我偶然发现了另一个解决方案。使用
preact compat
而不是
react
。在阴影域中似乎工作正常;preact必须以不同方式绑定事件?

此答案是五年后的更新

坏消息:由@josephnvu(在编写时接受)回答,并且
react shadow dom retarget events
包不再正常工作,至少在react 16.13.1中是如此-尚未使用早期版本进行测试。看起来react内部发生了更改,导致代码调用错误的侦听器回调

好消息:

  • 在React 16.13.1中(同样,未使用之前的16.x进行测试),可以直接渲染到卷影根中,而无需中间块。在这种情况下,侦听器将附加到卷影根而不是文档,因此React能够正确捕获和分派所有事件。明显的折衷是,您不能向同一卷影根添加任何其他内容,因为React将覆盖您的元素使用呈现的JSX的ents
  • 在React 17中,React将其侦听器附加到渲染根目录,而不是文档或阴影根目录,因此无论渲染到何处,一切都是开箱即用的

你提到的这个特例是什么?它只是为了实验吗?@Seth只是一个概念证明,看看我们是否可以将React组件封装在Web组件中,这样我们就可以在一些没有React作为主框架的应用程序中使用它。这可能是一种远取方法,但只是想看看是否可行。但我想知道使用阴影dom。否则它不会被封装。这里的挑战是将React组件封装在自定义元素中。我发现了。也许值得一看。谢谢@m
import retargetEvents from 'react-shadow-dom-retarget-events';

class App extends React.Component {
  render() {
    return (
        <div onClick={() => alert('I have been clicked')}>Click me</div>
    );
  }
}

const proto = Object.create(HTMLElement.prototype, {
  attachedCallback: {
    value: function() {
      const mountPoint = document.createElement('span');
      const shadowRoot = this.createShadowRoot();
      shadowRoot.appendChild(mountPoint);
      ReactDOM.render(<App/>, mountPoint);
      retargetEvents(shadowRoot);
    }
  }
});
document.registerElement('my-custom-element', {prototype: proto});