8

Mam specjalny przypadek, w którym muszę hermetyzować komponent odpowiedzi za pomocą składnika sieci Web. Konfiguracja wydaje się bardzo prosta. Oto kod React:Zdarzenie kliknięcia nie jest wyzwalane, gdy komponent reaguje w cieniu DOM

// 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> 

Ten wierzchowiec grzywny i prace zdarzeń kliknięcia. Teraz, gdy utworzyłem element opakowania składnika Web wokół komponentu odpowiedzi, renderuje się on poprawnie, ale zdarzenie kliknięcia nie działa. Oto mój składnik Web Wrapper:

// 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 
}); 

A oto HTML:

<box-webcomp></box-webcomp> 

Czy coś mi brakuje? Czy React odmawia pracy w komponencie sieciowym? Widziałem bibliotekę podobną do Maple.JS, która robi tego typu rzeczy, ale ich biblioteka działa. Czuję, że brakuje mi jednej małej rzeczy.

Oto CodePen tak widać problem:

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

+0

O czym jest ten specjalny przypadek, o którym mówisz? czy to tylko do eksperymentów? – Seth

+0

@Seth Just a Proof of concept, aby sprawdzić, czy możemy zamknąć składnik React w komponencie Web, abyśmy mogli go użyć w niektórych naszych aplikacjach, które nie mają React jako głównej struktury. Może to być podejście dalekosiężne, ale po prostu chcesz sprawdzić, czy to możliwe. – josephnvu

Odpowiedz

6

Okazuje się, że retargety Shadow DOM klikają zdarzenia i hermetyzują zdarzenia w cieniu. Reactowi to się nie podoba, ponieważ nie wspierają natywnego programu Shadow DOM, więc delegowanie wydarzeń jest wyłączone, a zdarzenia nie są uruchamiane.

Co postanowiłem zrobić, to ponownie powiązać zdarzenie z faktycznym kontenerem cienia, który jest technicznie "w świetle". Śledzę bulgotanie zdarzenia przy użyciu event.path i uruchamiam wszystkie procedury obsługi zdarzeń React w kontekście do kontenera cieni.

Dodałem metodę "retargetEvents", która wiąże wszystkie możliwe typy zdarzeń z kontenerem. Następnie wyśle ​​poprawne zdarzenie React, znajdując "__reactInternalInstances" i odszukając odpowiednią procedurę obsługi zdarzenia w zasięgu/ścieżce zdarzenia.

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; 
     } 

     }); 
    }); 
    } 

chciałbym wykonać „retargetEvents” Kiedy renderowania komponentu reagują do DOM cienia

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(); 
     } 
    }; 
    } 

Mam nadzieję, że to działa dla przyszłych wersjach React. Oto codePen z nim pracować:

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

Dzięki @mrlew na odnośnik, który dał mi wskazówkę, jak rozwiązać ten problem, a także dzięki @Wildhoney do myślenia na tych samych falach co ja =) .

+0

Świetne rozwiązanie. Ciekawe, dlaczego zespół reagujący nie chce go zintegrować. Widziałem dwa PR na ten temat. ( – Dimitry

+0

Czy "open-source" to? Znalazłem błąd (wiele wywołań dispatchEvent) i miło będzie go naprawić i udostępnić – Dimitry

+0

Och, człowiek chce dać ci 10.000 przegranych na ten temat. Uratowałeś mi życie – Lukas

0

Wymiana this.el = this.createShadowRoot(); z this.el = document.getElementById("mountReact"); właśnie pracował. Może dlatego, że reaguje na globalną obsługę zdarzeń, a shadow dom wymaga ponownego kierowania na wydarzenia.

+0

Ale chcę użyć cienia dom. W przeciwnym razie nie jest hermetyzowany. Wyzwanie polega na tym, że komponent React Owinięty w niestandardowy element. – josephnvu

+1

Znalazłem [to] (https://github.com/Wildhoney/ReactShadow). Może warto sprawdzić. – mrlew

+0

dzięki @mrlew. Ta część pomaga: "Jako, że Shadow DOM ma koncepcję Retargetowania zdarzeń do celów enkapsulacji, delegowanie zdarzeń nie będzie działać poprawnie, ponieważ wszystkie zdarzenia będą wyglądać tak, jakby pochodziły z DOM Cienia - dlatego ReactShadow używa identyfikatora React dla każdego elementu, aby wysłać zdarzenie z oryginalnego elementu, w związku z tym utrzymując wdrożenie delegacji zdarzeń React. " – josephnvu

2

Naprawiłem błąd usunięty z kodu @josephvnu's accepted answer.Opublikowałem go jako pakiet npm tutaj: https://www.npmjs.com/package/react-shadow-dom-retarget-events

Wykorzystanie idzie następująco

Install

yarn add react-shadow-dom-retarget-events lub

npm install react-shadow-dom-retarget-events --save

Korzystając

importować retargetEvents i wezwać go na 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}); 

Dla porównania, jest to pełny kod źródłowy z poprawki https://github.com/LukasBombach/react-shadow-dom-retarget-events/blob/master/index.js

0

odkryłem innego rozwiązania przez przypadek. Użyj preact-compat zamiast zamiast react. Wygląda na to, że działa dobrze w ShadowDOM; Preact musi inaczej wiązać się z wydarzeniami?

Powiązane problemy