Reactコンポーネントを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')
);
HTML:
<div id="mountReact"></div>
これは正常にマウントされ、クリックイベントが機能します。 React Componentの周りにWebコンポーネントラッパーを作成すると、正しくレンダリングされますが、クリックイベントが機能しません。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のようなライブラリを見たことがありますが、それらのライブラリは機能します。小さなライブラリが1つ欠けているように感じます。事。
これがCodePenなので、問題を確認できます。
結局のところ、Shadow DOMはクリックイベントを再ターゲットし、イベントをシャドウにカプセル化します。 ReactはShadowDOMをネイティブにサポートしていないため、これが気に入らないため、イベントの委任はオフになり、イベントは発生しません。
私がやろうと決めたのは、技術的に「光の中に」ある実際のシャドウコンテナにイベントを再バインドすることでした。 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の将来のバージョンで機能することを願っています。これが機能しているcodePenです:
http://codepen.io/homeslicesolutions/pen/ZOpbWb
これを修正する方法の手がかりを与えてくれたリンクを@mrlewに感謝し、私と同じ波長について考えてくれた@Wildhoneyにも感謝します=)。
@ josephvnuの受け入れられた回答 のコードをクリーンアップしたバグを修正しました。ここでnpmパッケージとして公開しました: https://www.npmjs.com/package/react-shadow-dom-retarget-events
使い方は次のとおりです
インストール
yarn add react-shadow-dom-retarget-events
または
npm install react-shadow-dom-retarget-events --save
使用
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});
参考までに、これは修正の完全なソースコードです https://github.com/LukasBombach/react-shadow-dom-retarget-events/blob/master/index.js
私は偶然に別の解決策を発見しました。 react
の代わりにpreact-compat
を使用してください。 ShadowDOMでは正常に機能しているようです。 Preactはイベントに異なる方法でバインドする必要がありますか?
this.el = this.createShadowRoot();
をthis.el = document.getElementById("mountReact");
に置き換えるだけでうまくいきました。おそらく、reactにはグローバルイベントハンドラーがあり、shadowdomはイベントのリターゲティングを意味するためです。