我有一个特殊的情况,我需要用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
事实证明,Shadow DOM重新定位单击事件并将事件封装在阴影中。React不喜欢这样做,因为它们本身不支持Shadow DOM,因此事件委托已关闭,事件也未触发。
我决定要做的是将事件重新绑定到实际的影子容器,该影子容器在技术上是“光明的”。我使用跟踪事件的冒泡,event.path并在上下文中将所有React事件处理程序触发到影子容器。
event.path
我添加了一个’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考虑与我相同的波长=)。