React: createPortal: Unterstützungsoption, um die Ausbreitung von Ereignissen im React-Baum zu stoppen

Erstellt am 27. Okt. 2017  ·  103Kommentare  ·  Quelle: facebook/react

Möchten Sie eine Funktion anfordern oder einen Fehler melden?
Feature, aber auch ein Fehler, der dazu führt, dass die neue API die alte unstable_rendersubtreeintocontainer bricht

Wie ist das aktuelle Verhalten?
Wir können die Ausbreitung aller Ereignisse vom Portal zu seinen React-Baum-Vorfahren nicht stoppen. Unser Layer-Mechanismus mit Modals/Popovers ist komplett kaputt. Zum Beispiel haben wir eine Dropdown-Schaltfläche. Wenn wir darauf klicken, klicken Sie auf öffnet Popover. Wir möchten auch dieses Popover schließen, wenn Sie auf dieselbe Schaltfläche klicken. Klicken Sie mit createPortal in das Popover-Feuer, klicken Sie auf die Schaltfläche und es wird geschlossen. In diesem einfachen Fall können wir stopPropagation verwenden. Aber wir haben jede Menge solcher Fälle, und wir müssen stopPropagation für alle verwenden. Außerdem können wir nicht alle Veranstaltungen stoppen.

Was ist das erwartete Verhalten?
createPortal sollte eine Option zum Stoppen der synthetischen Ereignisausbreitung durch den React-Baum haben, ohne jedes Ereignis manuell zu stoppen. Was denken Sie?

DOM Feature Request

Hilfreichster Kommentar

Auch das erscheint mir unnötig komplex. Warum fügen Sie createPortal nicht einfach ein optionales boolesches Flag hinzu, das das Blockieren des Bubbling-Verhaltens ermöglicht?

Alle 103 Kommentare

Außerdem sieht die Ausbreitung von mouseOver/Leave völlig unerwartet aus.
image

Können Sie das Portal außerhalb der Schaltfläche verschieben?

z.B

return [
  <div key="main">
    <p>Hello! This is first step.</p>
    <Button key="button" />
  </div>,
  <Portal key="portal" />
];

Dann sprudelt es nicht durch die Schaltfläche.

Es war mein erster Gedanke, aber!) Stellen Sie sich vor, wir haben den mouseEnter-Handler in einem solchen Komponentencontainer:

image

Mit unstable_rendersubtreeintocontainer brauche ich nichts mit Ereignissen in ButtonWithPopover zu tun – mouseEnter funktioniert einfach, wenn die Maus wirklich div und das DOM-Element der Schaltfläche betritt, und wird nicht ausgelöst, wenn die Maus über dem Popover ist. Beim Portal wird das Ereignis ausgelöst, wenn die Maus über das Popover-Fenster fährt – und in diesem Moment NICHT über div . Also muss ich die Ausbreitung stoppen. Wenn ich dies in der ButtonWithPopover Komponente mache, unterbreche ich die Ereignisauslösung, wenn sich die Maus über der Schaltfläche befindet. Wenn ich es in Popover mache und eine gängige Popover-Komponente für diese Anwendung verwende, kann ich auch die Logik in anderen App-Teilen unterbrechen.

Ich verstehe wirklich nicht, was es bedeutet, durch den React-Baum zu sprudeln. Wenn ich Ereignisse aus der Portalkomponente benötige, kann ich einfach Handler durch Requisiten übergeben. Wir haben es mit unstable_rendersubtreeintocontainer und es hat perfekt funktioniert.

Wenn ich ein modales Fenster über eine Schaltfläche tief im Reaktionsbaum öffne, erhalte ich unerwartete Auslösung von Ereignissen unter modal. stopPropagation wird auch die Verbreitung in DOM stoppen und ich werde keine Ereignisse bekommen, von denen ich wirklich erwarte, dass sie gefeuert werden (

@gaearon Ich würde vorschlagen, dass dies eher ein Fehler als eine Funktionsanfrage ist. Wir haben eine Reihe neuer Fehler, die durch Mausereignisse verursacht werden, die durch Portale sprudeln (wo wir zuvor unstable_rendersubtreeintocontainer ). Einige davon können nicht einmal mit einer zusätzlichen div-Ebene zum Filtern von Mausereignissen behoben werden, da wir beispielsweise darauf angewiesen sind, dass sich mousemove-Ereignisse bis zum Dokument ausbreiten, um ziehbare Dialoge zu implementieren.

Gibt es eine Möglichkeit, dies zu umgehen, bevor dies in einer zukünftigen Version behoben wird?

Ich denke, es wird als Feature-Request bezeichnet, weil das aktuelle Bubble-Verhalten von Portalen sowohl erwartet als auch beabsichtigt ist. Das Ziel ist, dass sich der Teilbaum wie ein echtes Kind seiner Eltern verhält.

Hilfreich wären zusätzliche Anwendungsfälle oder Situationen (wie die, die Sie gerade sehen), von denen Sie glauben, dass sie von der aktuellen Implementierung nicht erfüllt werden oder die nur schwer umgangen werden können

Ich verstehe, dass dieses Verhalten beabsichtigt ist, aber ich denke, es ist ein erheblicher Fehler, dass es nicht deaktiviert werden kann.

Meiner Meinung nach sollte eine Bibliothek, die mit DOM arbeitet, das Verhalten der DOM-Implementierung beibehalten und nicht brechen.

Zum Beispiel:

class Container extends React.Component {
  shouldComponentUpdate = () => false;
  render = () => (
    <div
      ref={this.props.containerRef}
      // Event propagation on this element not working
      onMouseEnter={() => { console.log('handle mouse enter'); }}
      onClick={() => { console.log('handle click'); }}
    />
  )
}

class Root extends React.PureComponent {
  state = { container: null };
  handleContainer = (container) => { this.setState({ container }); }

  render = () => (
    <div>
      <div
        // Event propagation on this element not working also
        onMouseEnter={() => { console.log('handle mouse enter'); }}
        onClick={() => { console.log('handle click'); }}
      >
        <Container containerRef={this.handleContainer} />
      </div>
      {this.state.container && ReactDOM.createPortal(
        <div>Portal</div>,
        this.state.container
      )}
    </div>
  );
}

Wenn ich mit DOM arbeite, erwarte ich Ereignisse wie die DOM-Implementierung. In meinem Beispiel werden Ereignisse über Portal verbreitet , wobei die DOM-Eltern umgangen werden, und dies kann als Fehler angesehen werden .

Leute, vielen Dank für die Diskussion, aber ich denke, es ist nicht so hilfreich zu argumentieren, ob etwas ein Fehler ist oder nicht. Stattdessen wäre es produktiver, die Anwendungsfälle und Beispiele zu diskutieren, die durch das aktuelle Verhalten nicht erfüllt werden, damit wir besser verstehen können, ob der aktuelle Weg der beste Weg für die Zukunft ist.

Im Allgemeinen möchten wir, dass die API eine Vielzahl von Anwendungsfällen handhabt, während sie andere hoffentlich nicht übermäßig einschränkt. Ich kann nicht für das Kernteam sprechen, aber ich würde mir vorstellen, dass es keine wahrscheinliche Lösung ist, es konfigurierbar zu machen. Im Allgemeinen strebt React nach einer konsistenten API gegenüber konfigurierbaren.

Ich verstehe auch, dass dieses Verhalten nicht der Funktionsweise des DOM entspricht, aber ich denke nicht, dass dies an sich ein guter Grund ist zu sagen, dass dies nicht so sein sollte. Vieles Verhalten von React-Dom unterscheidet sich von der Funktionsweise des DOM, viele Ereignisse unterscheiden sich bereits von der nativen Version. onChange zum Beispiel ist völlig anders als das native Änderungsereignis, und alle reagierenden Ereignisse blasen unabhängig vom Typ, im Gegensatz zum DOM.

Stattdessen wäre es produktiver, die Anwendungsfälle und Beispiele zu diskutieren, die durch das aktuelle Verhalten nicht erfüllt werden

Hier sind zwei Beispiele, die bei unserer Migration zu React 16 für uns defekt sind.

Erstens haben wir einen ziehbaren Dialog, der über eine Schaltfläche gestartet wird. Ich habe versucht, ein "Filter"-Element zu unserer Portal-Nutzung hinzuzufügen, das StopPropagation bei jedem Maus-* und Tasten*-Ereignis aufruft. Wir verlassen uns jedoch darauf, dass wir ein mousemove-Ereignis an das Dokument binden können, um die Drag-Funktion zu implementieren. Dies ist üblich, denn wenn der Benutzer die Maus mit einer signifikanten Geschwindigkeit bewegt, verlässt der Cursor die Grenzen des Dialogfelds und Sie müssen um die Mausbewegung auf einer höheren Ebene erfassen zu können. Das Filtern dieser Ereignisse unterbricht diese Funktionalität. Aber bei Portalen sprudeln die Maus- und Tastenereignisse aus dem Inneren des Dialogs zu der Schaltfläche, die ihn gestartet hat, wodurch verschiedene visuelle Effekte angezeigt werden und der Dialog sogar geschlossen wird. Ich glaube nicht, dass es realistisch ist, zu erwarten, dass jede Komponente, die über ein Portal gestartet wird, 10-20 Ereignishandler bindet, um diese Ereignisweitergabe zu stoppen.

Zweitens haben wir ein Popup-Kontextmenü, das entweder durch einen primären oder sekundären Mausklick gestartet werden kann. Einer der internen Verbraucher unserer Bibliothek hat Maus-Handler an das Element angehängt, das dieses Menü startet, und natürlich verfügt das Menü auch über Klick-Handler für die Handhabung der Elementauswahl. Das Menü wird jetzt bei jedem Klick wieder angezeigt, da die Mousedown-/Mousedown-Ereignisse wieder zu der Schaltfläche sprudeln, die das Menü startet.

Ich kann nicht für das Kernteam sprechen, aber ich würde mir vorstellen, dass es keine wahrscheinliche Lösung ist, es konfigurierbar zu machen. Im Allgemeinen strebt React nach einer konsistenten API gegenüber konfigurierbaren.

Ich bitte Sie (und das Team), diese Position in diesem speziellen Fall zu überdenken. Ich denke, Event-Bubbling wird für bestimmte Anwendungsfälle interessant sein (obwohl mir spontan keine einfällt). Aber ich denke, es wird bei anderen lähmend sein und es führt zu erheblichen Inkonsistenzen in der API. Obwohl unstable_rendersubtreeintocontainer nie super unterstützt wurde, war es das, was jeder außerhalb des unmittelbaren Baums gerendert hat, und so funktionierte es nicht. Es wurde offiziell zugunsten von Portalen eingestellt, aber Portale unterbrechen die Funktionalität auf diese kritische Weise, und es scheint keinen einfachen Workaround zu geben. Ich denke, dies kann als ziemlich inkonsistent bezeichnet werden.

Ich verstehe auch, dass dieses Verhalten nicht der Funktionsweise des DOM entspricht, aber ich denke nicht, dass dies an sich ein guter Grund ist zu sagen, dass dies nicht so sein sollte.

Ich verstehe, woher Sie kommen, aber ich denke, in diesem Fall (a) handelt es sich um ein grundlegendes Verhalten, das (b) derzeit keine Problemumgehung bietet, daher denke ich, dass "das DOM so nicht funktioniert" ein starkes Argument ist. wenn auch nicht ganz überzeugend.

Und um es klar zu sagen: Meine Bitte, dies als Fehler zu betrachten, besteht hauptsächlich darin, dass es eher früher als später für eine Fehlerbehebung priorisiert wird.

Mein mentales Modell eines Portals ist, dass es sich so verhält, als ob es sich an derselben Stelle im Baum befindet, aber Probleme wie "Überlauf: versteckt" vermeidet und das Scrollen zu Zeichen- / Layoutzwecken vermeidet.

Es gibt viele ähnliche "Popup"-Lösungen, die ohne Portal inline ausgeführt werden. ZB eine Schaltfläche, die ein Feld daneben erweitert.

Nehmen Sie als Beispiel den Dialog "Wählen Sie Ihre Reaktion" hier auf GitHub. Das ist als div direkt neben dem Button implementiert. Das funktioniert jetzt gut. Wenn es jedoch einen anderen Z-Index haben möchte oder aus einem overflow: scroll Bereich herausgehoben werden soll, der diese Kommentare enthält, muss es die DOM-Position ändern. Diese Änderung ist nicht sicher, es sei denn, andere Dinge wie Event-Bubbling werden ebenfalls beibehalten.

Beide Arten von "Popups" oder "Pop-Outs" sind legitim. Wie würden Sie also dasselbe Problem lösen, wenn die Komponente im Layout inline ist und nicht außerhalb davon schwebt?

Die Problemumgehung, die für mich funktioniert hat, ist der Aufruf von stopPropagation direkt unter Portal-Rendering:

return createPortal(
      <div onClick={e => e.stopPropagation()}>{this.props.children}</div>,
      this.el
    )

Das funktioniert für mich hervorragend, da ich eine einzelne Abstraktionskomponente habe, die Portale verwendet. Andernfalls müssen Sie alle Ihre createPortal Aufrufe reparieren.

@methyl Dies setzt voraus, dass Sie jedes Ereignis kennen, das Sie daran mousemove , um in das Dokument einzusteigen, aber nicht , um den Renderbaum aufzublasen.

Beide Arten von "Popups" oder "Pop-Outs" sind legitim. Wie würden Sie also dasselbe Problem lösen, wenn die Komponente im Layout inline ist und nicht außerhalb davon schwebt?

@sebmarkbage Ich bin mir nicht sicher, ob diese Frage Sinn macht. Wenn ich dieses Problem beim Inlinen der Komponente hätte, würde ich es nicht inline.

Ich denke, ein Problem hier ist, dass einige Anwendungsfälle von renderSubtreeIntoContainer auf createPortal portiert werden, wenn die beiden Methoden konzeptionell unterschiedliche Dinge tun. Das Konzept von Portal wurde meiner Meinung nach überladen.

Ich stimme zu, dass Sie im Fall des Modal-Dialogfelds fast nie möchten, dass sich das Modal wie ein untergeordnetes Element der Schaltfläche verhält, die es geöffnet hat. Die Triggerkomponente rendert sie nur, weil sie den Zustand open steuert. Ich halte es für einen Fehler zu sagen, dass die Portalimplementierung daher falsch ist, anstatt zu sagen, dass createPortal im Button dafür nicht das richtige Werkzeug ist. In diesem Fall ist Modal kein untergeordnetes Element des Triggers und sollte nicht so gerendert werden. Eine mögliche Lösung ist zu halten mit renderSubtreeIntoContainer , eine andere Benutzer-Land - Option ist eine haben ModalProvider in der Nähe der App Wurzel , dass Griffe modals Rendering und geht nach unten (via Kontext) ein Verfahren ein machen beliebiges modales Element muss zur Wurzel

renderSubtreeIntoContainer kann nicht innerhalb von render oder Lebenszyklusmethoden in React 16 aufgerufen werden, was die Verwendung für die Fälle, die ich besprochen habe, ziemlich ausschließt (tatsächlich alle unsere Komponenten, die dies machte die Migration auf 16 komplett kaputt). Portale sind die offizielle Empfehlung: https://reactjs.org/blog/2017/09/26/react-v16.0.html#breaking -changes

Ich stimme zu, dass das Konzept von Portalen möglicherweise überladen ist. Ich bin mir jedoch nicht sicher, ob ich die Lösung einer globalen Komponente und den Kontext dafür liebe. Es scheint, als könnte dies leicht durch ein Flag in createPortal gelöst werden, das angibt, ob Ereignisse durchgeblasen werden sollen. Es wäre ein Opt-in-Flag, das die API-Kompatibilität mit 16+ aufrechterhält.

Ich werde versuchen, unseren Anwendungsfall der Portale zu klären und warum wir gerne eine Option sehen würden, um die Verbreitung von Ereignissen zu stoppen. In der ManyChat-App verwenden wir Portale, um "Ebenen" zu erstellen. Wir haben das Ebenensystem für die gesamte App, das von mehreren Arten von Komponenten verwendet wird: Popover, Dropdowns, Menüs, Modal. Jede Ebene kann eine neue Ebene freigeben, zB kann eine Schaltfläche auf einer zweiten Menüebene das modale Fenster auslösen, wo die andere Schaltfläche das Popover öffnen kann. In den meisten Fällen ist Layer der neue Zweig von UX, der seine eigene Aufgabe löst. Und wenn eine neue Ebene geöffnet ist, sollte der Benutzer mit dieser neuen Ebene interagieren, nicht mit anderen unten. Für dieses System haben wir also eine gemeinsame Komponente zum Rendern auf Ebenen erstellt:

class RenderToLayer extends Component {
  ...
  stop = e => e.stopPropagation()

  render() {
    const { open, layerClassName, useLayerForClickAway, render: renderLayer } = this.props

    if (!open) { return null }

    return createPortal(
      <div
        ref={this.handleLayer}
        style={useLayerForClickAway ? clickAwayStyle : null}
        className={layerClassName}
        onClick={this.stop}
        onContextMenu={this.stop}
        onDoubleClick={this.stop}
        onDrag={this.stop}
        onDragEnd={this.stop}
        onDragEnter={this.stop}
        onDragExit={this.stop}
        onDragLeave={this.stop}
        onDragOver={this.stop}
        onDragStart={this.stop}
        onDrop={this.stop}
        onMouseDown={this.stop}
        onMouseEnter={this.stop}
        onMouseLeave={this.stop}
        onMouseMove={this.stop}
        onMouseOver={this.stop}
        onMouseOut={this.stop}
        onMouseUp={this.stop}

        onKeyDown={this.stop}
        onKeyPress={this.stop}
        onKeyUp={this.stop}

        onFocus={this.stop}
        onBlur={this.stop}

        onChange={this.stop}
        onInput={this.stop}
        onInvalid={this.stop}
        onSubmit={this.stop}
      >
        {renderLayer()}
      </div>, document.body)
  }
  ...
}

Diese Komponente stoppt die Weitergabe für alle Ereignistypen aus React-Dokumenten und ermöglichte uns, auf React 16 zu aktualisieren.

Muss das an Portale gebunden werden? Anstelle von Sandboxing-Portalen, was wäre, wenn es nur ein (zum Beispiel) <React.Sandbox>...</React.Sandbox> gäbe?

Auch das erscheint mir unnötig komplex. Warum fügen Sie createPortal nicht einfach ein optionales boolesches Flag hinzu, das das Blockieren des Bubbling-Verhaltens ermöglicht?

@gaearon das ist eine ziemlich unglückliche Situation für einen bestimmten Teil von uns - könnten Sie oder jemand, der Ihnen am Herzen liegt, sich das ansehen? :)

Ich möchte hinzufügen, dass ich derzeit denke, dass beide Anwendungsfälle unterstützt werden sollten. Es gibt wirklich Anwendungsfälle, in denen Kontext vom aktuellen Elternteil zum Teilbaum fließen muss, dieser Teilbaum jedoch nicht als logisches Kind im Sinne des DOM agiert. Komplexe Modalelemente sind das beste Beispiel, Sie möchten nur fast nie, dass sich die Ereignisse aus einem Formular in einem modalen Fenster bis zur Trigger-Schaltfläche ausbreiten, aber mit ziemlicher Sicherheit müssen Sie den Kontext durchlaufen (i18n, Themen usw.).

Ich werde sagen, dass dieser Anwendungsfall meistens mit einem ModalProvider gelöst werden könnte, der näher am App-Stamm liegt und über createPortal hoch genug rendert, dass die Ereignisausbreitung nichts beeinflusst, aber das fühlt sich an wie ein Workaround im Gegensatz zu a gut durchdachte Architektur. Es macht auch von der Bibliothek bereitgestellte Modalitäten für Benutzer ärgerlicher, da sie nicht mehr in sich geschlossen sind.

Ich würde tho in Bezug auf die API hinzufügen. Ich denke nicht, dass createPortal beides tun sollte, der modale Fall möchte wirklich nur ReactDOM.render (alte Schule) verwenden, da er ziemlich nahe an einem bestimmten Baum ist _außer_ dass Kontextpropagation oft erforderlich ist

Wir mussten nur einen äußerst schwer zu diagnostizierenden Fehler im Fokusverwaltungscode unserer äußeren Anwendung beheben, der auf die von Problemumgehung zurückzuführen war .

Genauer gesagt: Das Aufrufen von stopPropagation für das synthetische Fokusereignis, um zu verhindern, dass es aus dem Portal sprudelt, bewirkt, dass stopPropagation auch für das native Fokusereignis in Reacts erfasstem Handler auf #document aufgerufen wird, was bedeutet, dass es nicht zu einem anderen erfassten Handler auf <body> gelangt ist

Das neue sprudelnde Verhalten in Portalen fühlt sich für mich wirklich wie ein Minderheitenfall an. Seien es diese Meinung oder Wahrheit, könnten wir bitte etwas zu diesem Thema finden? Vielleicht @gaearon? Es ist vier Monate alt und verursacht echte Schmerzen. Ich denke, dies könnte durchaus als Fehler bezeichnet werden, da es sich um eine bahnbrechende API-Änderung in React 16 ohne völlig sichere Problemumgehung handelt.

@craigkovatch Ich bin immer noch gespannt, wie Sie mein Inline-Beispiel lösen würden. Nehmen wir an, das Popup drückt die Größe der Box nach unten. Etwas einzureihen ist wichtig, da es aufgrund seiner Größe etwas im Layout nach unten drückt. Es kann nicht einfach darüber schweben.

Sie könnten möglicherweise das Popover messen, einen leeren Platzhalter mit der gleichen Größe einfügen und versuchen, ihn oben auszurichten, aber das tun die Leute nicht.

Wenn Ihr Popover also den Inhalt an Ort und Stelle erweitern muss, z. B. direkt neben der Schaltfläche, wie würden Sie dies lösen? Ich vermute, dass das Muster, das dort funktioniert, in beiden Fällen funktioniert und wir nur das eine Muster empfehlen sollten.

Ich denke, im Allgemeinen ist dies das Muster, das in beiden Szenarien funktioniert:

class Foo extends React.Component {
  state = {
    highlight: false,
    showFlyout: false,
  };

  mouseEnter() {
    this.setState({ highlight: true });
  }

  mouseLeave() {
    this.setState({ highlight: false });
  }

  showFlyout() {
    this.setState({ showFlyout: true });
  }

  hideFlyout() {
    this.setState({ showFlyout: false });
  }

  render() {
    return <>
      <div onMouseEnter={this.mouseEnter} onMouseLeave={this.mouseLeave} className={this.state.highlight ? 'highlight' : null}>
        Hello
        <Button onClick={this.showFlyout} />
      </div>
      {this.state.showFlyout ? <Flyout onHide={this.hideFlyout} /> : null}
    </>;
  }
}

Wenn das Flyout ein Portal ist, funktioniert es und es wird keine Maus über Ereignisse angezeigt, wenn Sie mit der Maus über das Portal fahren. Aber was noch wichtiger ist, es funktioniert auch, wenn es KEIN Portal ist und es ein Inline-Flyout sein muss. Keine stopPropagation erforderlich.

Was ist also an diesem Muster, das für Ihren Anwendungsfall nicht funktioniert?

@sebmarkbage wir verwenden Portals auf eine völlig andere Art und Weise, <body> eingehängt ist, das dann positioniert wird, manchmal mit einem Z-Index. Die React-Dokumentation legt nahe, dass dies der Designabsicht näher kommt; dh das Rendern an eine ganz andere Stelle im DOM. Es scheint mir nicht, dass unsere Anwendungsfälle ähnlich genug sind, um eine Diskussion in diesen Thread aufzunehmen. Aber wenn Sie gemeinsam ein Brainstorming/Troubleshooting durchführen möchten, würde ich mich sehr freuen, in einem anderen Forum weiter darüber zu diskutieren.

Nein, mein Anwendungsfall ist beides . Manchmal das eine und manchmal das andere. Deshalb ist es relevant.

Das <Flyout /> kann wählen, ob es in das letzte untergeordnete Element von body gerendert werden soll oder nicht, aber solange Sie das Portal selbst zu einem Geschwister der schwebenden Komponente statt zu einem untergeordneten Element davon hochheben, funktioniert Ihr Szenario.

Ich denke, es gibt ein plausibles Szenario, in dem dies unbequem ist und Sie eine Möglichkeit möchten, Dinge aus tief verschachtelten Komponenten zu teleportieren, aber in diesem Szenario sind Sie wahrscheinlich damit einverstanden, dass der Kontext der Kontext vom Zwischenpunkt ist. Aber ich betrachte das als zwei getrennte Themen.

Vielleicht brauchen wir dafür eine Slots-API.

class Foo extends React.Component {
  state = {
    showFlyout: false,
  };

  showFlyout() {
    this.setState({ showFlyout: true });
  }

  hideFlyout() {
    this.setState({ showFlyout: false });
  }

  render() {
    return <>
      Hello
      <Button onClick={this.showFlyout} />
      <SlotContent name="flyout">
        {this.state.showFlyout ? <Flyout onHide={this.hideFlyout} /> : null}
      </SlotContent>
    </>;
  }
}

class Bar extends React.Component {
  state = {
    highlight: false,
  };

  mouseEnter() {
    this.setState({ highlight: true });
  }

  mouseLeave() {
    this.setState({ highlight: false });
  }

  render() {
    return <>
      <div onMouseEnter={this.mouseEnter} onMouseLeave={this.mouseLeave} className={this.state.highlight ? 'highlight' : null}>
        <SomeContext>
          <DeepComponent />
        </SomeContext>
      </div>
      <Slot name="flyout" />
    </>;
  }
}

Das Portal würde dann den Kontext von Bar erhalten, nicht von DeepComponent. Kontext- und Ereignis-Bubbling teilen sich dann immer noch denselben Baumpfad.

@sebmarkbage Der modale Fall erfordert normalerweise Kontext ab dem Punkt, an dem er gerendert wird. Es ist ein etwas einzigartiger Fall, denke ich, dass die Komponente ein logisches Kind des Dings ist, das sie gerendert hat, aber _kein_ ein strukturelles (mangels eines besseren Wortes), zB Sie wollen normalerweise Dinge wie Formularkontext (Relais, Formik, Redux-Form) , was auch immer), aber keine DOM-Ereignisse, die durchlaufen werden müssen. Am Ende rendert man solche Modal auch ziemlich tief in Bäumen, neben ihren Triggern, so dass sie komponentig und wiederverwendbar bleiben, mehr als weil sie strukturell dort hingehören

Ich denke, dieser Fall unterscheidet sich im Allgemeinen von dem Flyout-/Dropdown-Fall, den createPortal serviert. Tbc Ich finde das Sprudelverhalten von Portalen gut, aber nicht für Modals. Ich denke auch, dass dies mit Context und einer Art ModalProvider einigermaßen gut gelingen könnte, aber das ist insbesondere für Bibliotheken etwas nervig.

solange Sie das Portal selbst zu einem Geschwister der schwebenden Komponente statt zu einem Kind davon hochheben, funktioniert Ihr Szenario.

Ich bin mir nicht sicher, ob ich folge. Es gibt immer noch das Problem, dass zB keyDown-Ereignisse durch einen unerwarteten DOM-Baum sprudeln.

@jquense Beachten Sie, dass sich der Slot in meinem Beispiel immer noch in der Bar-Komponente befindet, sodass er seinen Kontext aus dem Formular in etwa <Form><Bar /></Form> erhält.

Auch wenn das Portal in den Dokumentkörper gerendert wurde.

Es ist also wie zwei Umwege (Portalings): tief -> Geschwister von Bar -> Dokumentkörper.

Der Kontext des Portals ist also immer noch der Kontext des Formulars, ebenso wie die Event-Bubbling-Kette, aber auch nicht der Kontext des schwebenden Dings.

Ja, das habe ich leider übersehen 😳 Wenn ich es richtig lese, hättest du aber immer noch das Sprudeln bei <Slot> ? Das ist definitiv besser, obwohl ich denke, dass man im Fall des modalen Dialogs wahrscheinlich kein _jedes_ Sprudeln möchte. Wie bei einem Screenreader möchten Sie, dass alles außerhalb des Modal invertiert wird, während es aktiv ist. Ich weiß nicht, ich denke, für diesen Fall ist das Blubbern ein Problem, niemand würde erwarten, dass ein Klick in einem Dialog irgendwo durchsprudelt.

Vielleicht sind das Problem hier nicht Portale, aber es gibt keine gute Möglichkeit, Kontext über Bäume hinweg zu teilen? Ein Teil des Kontexts ReactDOM.render ist wirklich gut für Modals und vielleicht sowieso "richtiger" darüber nachzudenken ...

Ich denke hier, dass es etwas sprudelt, weil es immer noch vom Modal zum div zum Körper zum Dokument zum Fenster geht. Und konzeptionell über den Rahmen hinaus zum umgebenden Fenster und so weiter.

Das ist bei etwas wie ART- oder GL-gerenderten Inhalten (und bis zu einem gewissen Grad React Native) nicht theoretisch, wo es möglicherweise keinen vorhandenen Backing-Tree gibt, um diese Semantik zu erhalten. Also muss es ein Weg , um zu sagen , dass das ist , wo es Blasen.

In einigen Apps gibt es Modale in Modalen. ZB bei FB gibt es ein Chatfenster, das sich über einem Modal befinden könnte oder ein Modal könnte Teil des Chatfensters sein. Auch ein Modal hat also einen gewissen Kontext, wo im Baum es hingehört. Es ist nie völlig eigenständig.

Das soll nicht heißen, dass wir nicht zwei verschiedene Semantiken für Event-Bubbling und Kontext haben können. Das sind beide explizit und Sie können das eine ohne das andere portal usw.

Die Garantie, dass beide dem gleichen Pfad folgen, ist jedoch sehr mächtig, da das Event-Bubbling für User-Space-Ereignisse genauso wie im Browser vollständig implementiert werden kann.

Dies geschieht beispielsweise heute mit verschiedenen Redux-Kontexten. Stellen Sie sich vor, this.context.dispatch("Hover") ist ein sprudelndes User-Space-Event. Wir könnten sogar React-Ereignisse als Teil des Kontexts implementieren. Es scheint vernünftig zu glauben, dass ich dies auf die gleiche Weise verwenden kann, und Sie können es jetzt in jeder Hinsicht. Ich denke, wenn wir diese beiden Kontexte verzweigen würden, würden wir wahrscheinlich mit einer anderen User-Space-Kontext-API enden, die der DOM-Struktur parallel zum normalen Kontext folgt - wenn sie wirklich so unterschiedlich sind.

Das ist der Grund, warum ich ein bisschen dagegen vorgehe, um zu sehen, ob die Slots-Sache ausreicht, da a) Sie explizit angeben müssen, welcher Kontext überhaupt sprudelt. b) es kann vermeiden, die Welt zu verzweigen und zwei vollständige Kontextsysteme zu haben.

Genauer gesagt: Das Aufrufen von stopPropagation für das synthetische Fokusereignis, um zu verhindern, dass es aus dem Portal sprudelt, bewirkt, dass stopPropagation auch für das native Fokusereignis in Reacts erfasstem Handler auf #document aufgerufen wird, was bedeutet, dass es es nicht zu einem anderen erfassten Handler auf . geschafft hat

. Wir haben das behoben, indem wir unseren Handler auf #document verschoben haben, aber wir hatten dies in der Vergangenheit ausdrücklich vermieden, um React nicht auf die Zehen zu treten.

@craigkovatch , haben Sie das Ereignis onFocusCapture im Dokument verwendet? In meiner Problemumgehung sollten erfasste Ereignisse nicht gestoppt werden. Können Sie ein detaillierteres Beispiel dafür geben, wie es war und was Sie unternommen haben, um Ihr Problem zu lösen?
Außerdem denke ich, dass mein Code ein Problem mit dem Stoppen des blur -Ereignisses hat – es sollte nicht gestoppt werden. Also werde ich dieser Frage genauer nachgehen und versuchen, eine zuverlässigere Lösung zu finden.

@kib357 Ich Problemumgehung gibt, ich denke, es gibt dort einen separaten Fehler in React (dh sollte die Weitergabe nativer Fokusereignisse in der Capture-Phase nicht abbrechen, wenn stopPropagation für synthetische Fokusereignisse in der Bubbling-Phase aufgerufen wird).

Der fragliche Code verwendet einen nativen Erfassungsereignis-Listener, dh document.body.addEventListener('focus', handler, true)

@craigkovatch klingt interessant, da Sie den erfassten Handler verwendet haben. Ich habe jedoch keine Gedanken, warum dies passiert.

Also, Leute, wir haben zwei verschiedene Szenarien für die Verwendung von Portal-Rendering:

  1. Um CSS-Probleme wie Overflow:hidden usw. in einfachen Widgets wie Dropdown-Schaltflächen oder einstufigen Menüs zu vermeiden
  2. So erstellen Sie eine neue UX-Ebene für leistungsstärkere Fälle wie:
  3. Modalitäten
  4. verschachtelte Menüs
  5. Popover-mit-Formularen-mit-Dropdowns-... – alle Fälle, wenn Ebenen kombiniert werden

Ich denke, die aktuelle createPortal API erfüllt nur das erste Szenario. Der Vorschlag, für den zweiten ein neues React.render zu verwenden, ist unbrauchbar – es ist sehr schlecht, eine separate App mit allen Anbietern für jede Ebene zu erstellen.
Welche zusätzlichen Informationen können wir bereitstellen, um dieses Problem zu beheben?
Welche Nachteile hat der vorgeschlagene Parameter in der createPortal API?

@sebmarkbage Meine unmittelbare Frage Könnte ich mehrere SlotContents in ein Slot einfügen? Es ist nicht ungewöhnlich, dass in unserer Benutzeroberfläche mehrere "Popups" oder "Modals" gleichzeitig geöffnet sind. In meiner perfekten Welt würde eine Popup API ungefähr so ​​aussehen:

import { App } from './app'
import { PopupSlot } from './popups'

let root = (
  <div>
    <App />
    <PopupSlot />
  </div>
)

ReactDOM.render(root, document.querySelector('#root'))

// some dark corner of our app

import { Popup } from './popups'

export function SoManyPopups () {
  return <>
    <Popup>My Entire</Popup>
    <Popup>Interface</Popup>
    <Popup>Is Popups</Popup>
  </>
}

Wir haben ein neues Problem damit, für das ich keine Problemumgehung finden konnte. Mit dem oben vorgeschlagenen "Ereignisfalle"-Ansatz werden nur React Synthetic-Ereignisse daran gehindert, aus dem Portal zu sprudeln. Die nativen Ereignisse sprudeln immer noch, und da unser React-Code in einer hauptsächlich jQuery-Anwendung gehostet wird, ruft der globale jQuery-keyDown-Handler auf <body> immer noch das Ereignis ab.

Ich habe versucht, dem nativen Containerelement im Portal einen event.stopPropagation-Listener über eine solche Referenz hinzuzufügen, aber dies neutralisiert alle synthetischen Ereignisse im Portal vollständig - ich habe fälschlicherweise angenommen, dass der oberste Listener von React die Erfassungsphase beobachtet.

Ich bin mir nicht sicher, was hier möglich ist, außer Änderungen an React.

const allTheEvents: string[] = 'click contextmenu doubleclick drag dragend dragenter dragexit dragleave dragover dragstart drop mousedown mouseenter mouseleave mousemove mouseover mouseout mouseup keydown keypress keyup focus blur change input invalid submit'.split(' ');
const stop = (e: React.SyntheticEvent<HTMLElement>): void => { e.stopPropagation(); };
const nativeStop = (e: Event): void => e.stopPropagation();
const handleRef = (ref: HTMLDivElement | null): void => {
  if (!ref) { return; }
  allTheEvents.forEach(eventName => ref.addEventListener(eventName, nativeStop));
};


/** Prevents https://reactjs.org/docs/portals.html#event-bubbling-through-portals */
export function PortalEventTrap(children: React.ReactNode): JSX.Element {
  return <div
      onClick={stop}
      ...

      ref={handleRef}
    >
      {children}
    </div>;
}

Das hängt von der Reihenfolge ab, in der ReactDOM und JQuery initialisiert werden. Wenn JQuery zuerst initialisiert wird, werden die Ereignishandler der obersten Ebene von JQuery zuerst installiert und werden daher ausgeführt, bevor die synthetischen Handler von ReactDOM ausgeführt werden.

Sowohl ReactDOM als auch JQuery bevorzugen es, nur einen einzigen Top-Level-Listener zu haben, der dann intern das Sprudeln simuliert, es sei denn, es gibt ein Ereignis, bei dem der Browser nicht sprudelt, wie z. B. scroll .

@Kovensky Mein Verständnis war, dass jQuery kein "synthetisches Sprudeln" wie bei React durchführte und daher keinen einzigen Listener der obersten Ebene hat. Mein DOM-Inspektor verrät auch keinen. Würde gerne sehen, was Sie meinen, wenn ich mich irre.

Dies wird bei delegierten Veranstaltungen der Fall sein. Beispiel: $(document.body).on('click', '.my-selector', e => e.stopPropagation()) .

Schauen Sie, dies kann in React gelöst werden, wenn mich jemand davon überzeugt, dass dies mit meinem oben vorgeschlagenen Design nicht gelöst werden kann, das eine Umstrukturierung Ihres Codes erfordert. Aber ich habe keinen Grund gesehen, der nicht anders gemacht werden kann, als nur zu versuchen, eine schnelle Problemumgehung zu finden.

@sebmarkbage Ihr Vorschlag löst nur den Fall, dass sich die Ereignisse an den unmittelbaren Eigentümer ausbreiten. Was ist mit dem Rest des Baumes?

Hier ist ein Anwendungsfall, der meiner Meinung nach mit Slots oder createPortal nicht gut gelöst werden kann

<Form defaultValue={fromValue}>
   <more-fancy-markup />
   <div>
     <Field name="faz"/>
     <ComplexFieldModal>
       <Field name="foo.bar"/>
       <Field name="foo.baz"/>
     </ComplexFieldModal>
  </div>
</Form>

Und hier ist ein Gif mit einem ähnlichen, aber etwas anderen Setup, bei dem ich createPortal für eine responsive Site verwende, um ein Formularfeld in die App-Symbolleiste (viel höher im Baum) zu verschieben. Auch in diesem Fall möchte ich wirklich nicht, dass Ereignisse zurück zum Seiteninhalt sprudeln, aber ich möchte auf jeden Fall, dass der Formularkontext dazu passt. Meine Implementierung ist übrigens eine Slot-artige Sache, die Kontext verwendet ...

large gif 640x320

@sebmarkbage unstable_renderSubtreeIntoContainer ermöglichte den direkten Zugriff auf die Spitze der Hierarchie, unabhängig von der Position einer Komponente, entweder innerhalb der Hierarchie oder als Teil eines separaten gepackten Frameworks.

Im Vergleich dazu sehe ich bei der Slot-Lösung ein paar Probleme:

  • Die Lösung geht davon aus, dass Sie Zugriff auf die Position der Hierarchie haben, an der Blasenereignisse "ok" sind. Dies ist bei Komponenten und Komponenten-Frameworks definitiv nicht der Fall.
  • Geht davon aus, dass es "ok" ist, Ereignisse auf jeder anderen Ebene der Hierarchie zu blasen.
  • Ereignisse werden immer noch von der Position des Slots aus sprudeln. (wie @craigkovatch erwähnt)

Ich habe auch einen Anwendungsfall (wahrscheinlich ähnlich den bereits erwähnten).

Ich habe eine Oberfläche, auf der Benutzer mit ihrer Maus mit einem "Lasso" Dinge auswählen können. Dies ist im Grunde 100% Breite/Höhe und befindet sich im Stammverzeichnis meiner App und verwendet das Ereignis onMouseDown . In dieser Oberfläche gibt es auch Schaltflächen, die Portale wie Modals und Dropdowns öffnen. Ein mouseDown-Ereignis innerhalb des Portals wird tatsächlich von der Lasso-Auswahlkomponente im Stammverzeichnis der App abgefangen.

Ich sehe viele, um das Problem zu beheben:

  • Rendern Sie das Portal einen Schritt über der Root-Lasso-Komponente, aber dies ist nicht sehr praktisch und müsste wahrscheinlich auf eine kontextbasierte Bibliothek wie React-Gateway zurückgreifen? (oder vielleicht das erwähnte Slot-System).
  • die Fortpflanzung innerhalb der Portalwurzel manuell stoppen, könnte aber zu den oben erwähnten unerwünschten Nebenwirkungen führen
  • Fähigkeit, die Ausbreitung in React-Portalen zu stoppen (+1 btw)
  • Ereignisse herausfiltern, wenn sie von einem Portal kommen

Im Moment besteht meine Lösung darin, die Ereignisse herauszufiltern.

const appRootNode = document.getElementById('root');

const isInPortal = element => isNodeInParent(element, appRootNode);


    handleMouseDown = e => {
      if (!isInPortal(e.target)) {
        return;
      }
      ...
    };

Dies wird eindeutig nicht die beste Lösung für uns alle sein und wird nicht sehr schön sein, wenn Sie verschachtelte Portale haben, aber für meinen aktuellen Anwendungsfall (der derzeit der einzige ist) funktioniert es. Ich möchte keine neue Kontextbibliothek hinzufügen oder eine komplexe Umgestaltung durchführen, um dies zu lösen. Wollte nur meine Lösung teilen.

Ich konnte das Blockieren von Event-Bubbling erreichen, wie an anderer Stelle in diesem Thread erwähnt.

Aber ein anderes, scheinbar dorniges Problem, auf das ich stoße, ist das onMouseEnter SyntheticEvent, das nicht sprudelt. Vielmehr geht es vom gemeinsamen Elternteil der from Komponente zur to Komponente, wie hier beschrieben. Dies bedeutet, dass, wenn der Mauszeiger von außerhalb des Browserfensters eindringt, jeder onMouseEnter Handler vom oberen Rand des DOM bis zur Komponente in createPortal in dieser Reihenfolge ausgelöst wird, wodurch alle möglichen Ereignisse ausgelöst werden, die noch nie mit unstable_renderSubtreeIntoContainer . Da onMouseEnter nicht sprudelt, kann es nicht auf Portalebene blockiert werden. (Dies schien bei unstable_renderSubtreeIntoContainer kein Problem zu sein, da das onMouseEnter Ereignis die virtuelle Hierarchie nicht respektiert und nicht durch den Hauptteilinhalt sequenziert, sondern direkt in den Unterbaum abstieg.)

Wenn jemand eine Idee hat, wie Sie verhindern können, dass sich onMouseEnter Ereignisse von der Spitze der DOM-Hierarchie ausbreiten oder direkt in die Portal-Unterstruktur umgeleitet werden, teilen Sie mir dies bitte mit.

@JasonGore Dieses Verhalten ist mir auch aufgefallen.

Zum Beispiel.

Ich habe ein Kontextmenü, das gerendert wird, wenn ein div onMouseOver auslöst, dann öffne ich ein Modal mit createPortal, indem ich auf eines der Elemente im Menü klicke. Wenn ich die Maus aus dem Browserfenster bringe, propagiert das Ereignis onMouseLeave bis zum Kontextmenü und schließt das Kontextmenü (und damit das Modal)...

Hatte das gleiche Problem, bei dem ich ein Listenelement hatte, das vollständig anklickbar sein sollte (als Link), aber eine Schaltfläche zum Löschen auf Labels unter dem Namen wollte, die ein Modal zur Bestätigung öffnen würde.

screenshot 2018-10-31 at 11 42 47

Meine einzige Lösung bestand darin, das Sprudeln auf dem modalen Div so zu verhindern:

// components/Modal.js

onClick(e) {
    e.stopPropagation();
}

return createPortal(
        <div onClick={this.onClick} ...
            ...

Es verhindert das Sprudeln bei jedem Modal ja, aber ich habe noch keinen Fall, in dem ich möchte, dass das passiert, also funktioniert es für mich.

Gibt es potenzielle Probleme mit diesem Ansatz?

@jnsandrew vergiss nicht, dass es ~50 andere

Schlagen Sie einfach darauf. Es erscheint mir peinlich, dass sich React anders verhält als das DOM-Event-Bubbling.

+1 dazu. Wir verwenden React.createPortal zum Rendern innerhalb des Iframes (sowohl für Stile als auch für die Isolierung von Ereignissen) und können nicht verhindern, dass Ereignisse sofort sprudeln, ist eine Schande.

Sieht so aus, als wäre dies das Problem mit den meisten Daumen hoch im React-Backlog. Zumindest sind die Dokumente dazu offen https://reactjs.org/docs/portals.html#event -bubbling-through-portals - obwohl sie die Nachteile oder Problemumgehungen nicht erwähnen und stattdessen darauf hinweisen, dass es "flexiblere Abstraktionen" ermöglicht ":

Die Dokumentation sollte zumindest erklären, dass dies zu Problemen führen kann und Problemumgehungen vorschlagen. In meinem Fall ist es ein ziemlich einfacher Anwendungsfall mit https://github.com/reactjs/react-modal : Ich habe Schaltflächen, die Dinge wie Dropdowns öffnen, und darin befinden sich Schaltflächen, die Modals erstellen. Wenn Sie auf die modale Sprechblase nach oben klicken, werden unerwünschte Dinge ausgeführt. Die Modalelemente werden in eine zusammenhängende Komponente gekapselt und das Herausziehen des mit Portal versehenen Teils unterbricht diese Kapselung und erzeugt eine undichte Abstraktion. Eine Problemumgehung könnte darin bestehen, ein Flag umzudrehen, um diese Schaltflächen zu deaktivieren, während das Modal geöffnet ist. Und natürlich kann ich die Propagation auch stoppen, wie oben vorgeschlagen, obwohl ich das in einigen Fällen möglicherweise nicht möchte.

Ich bin mir nicht sicher, wie hilfreich Bubbling und Capture im Allgemeinen sind (obwohl ich weiß, dass React auf Bubbling unter der Haube angewiesen ist) - sie haben sicherlich eine bewegte Geschichte, aber ich würde lieber einen Rückruf weiterleiten oder ein spezifischeres Ereignis propagieren (z. Redux-Aktion) als Bubble-Up oder Capture-Down, da solche Dinge wahrscheinlich über einen Haufen unnötiger Vermittler laufen. Es gibt Artikel wie https://css-tricks.com/dangers-stopping-event-propagation/ und ich arbeite an Apps, die auf die Weitergabe an den Körper angewiesen sind, hauptsächlich um Dinge zu schließen, wenn man auf "draußen" klickt, aber ich würde lieber Legen Sie eine unsichtbare Überlagerung über alles und schließen Sie, wenn Sie darauf klicken. Natürlich konnte ich das Portal von React nicht verwenden, um eine solche unsichtbare Überlagerung zu erstellen ...

Hier gibt es auch einen Wartungsalbtraum – wenn neue Events zum DOM hinzugefügt werden, werden alle Portale, die mit der oben besprochenen Technik "versiegelt" wurden, diese neuen Events "durchsickern", bis die Betreuer sie der (umfangreichen) schwarzen Liste hinzufügen können.

Hier gibt es ein großes Designproblem, das angegangen werden muss. Eine Möglichkeit, das Cross-Portal-Bubbling zu aktivieren oder zu deaktivieren, scheint mir immer noch die beste API-Option zu sein. Ich bin mir nicht sicher, was die Implementierungsschwierigkeiten darin enthalten, aber wir bekommen in Tableau nach über einem Jahr immer noch Produktionsfehler.

Verbringen Sie 2 Stunden damit, herauszufinden, warum mein Formular von Modal ein anderes Formular gesendet hat.
Endlich habe ich es dank dieses Problems herausgefunden!

Es fällt mir wirklich schwer zu erkennen, wann eine onSubmit Vermehrung notwendig sein könnte. Höchstwahrscheinlich wird es immer eher ein Fehler als ein Feature sein.

Es lohnt sich zumindest, einige Warnhinweise hinzuzufügen, um auf Dokumente reagieren zu können . Etwas wie das:
Während Ereignis-Bubbling durch Portale eine großartige Funktion ist, möchten Sie manchmal möglicherweise eine gewisse Ereignisausbreitung verhindern. Sie können dies erreichen, indem Sie onSubmit={(e) => {e.stopPropagation()}} hinzufügen

+1 dazu auch. Wir verwenden viel Draftjs mit anklickbarem Text, der Modals anzeigt. Und alle modalen Ereignisse wie Fokus, Auswahl, Änderung, Tastendruck usw. explodieren nur Draftjs mit Fehlern.

IMO, das Ereignis-Proxy-Verhalten ist grundlegend defekt (und verursacht bei mir auch Fehler), aber ich erkenne an, dass dies umstritten ist. Dieser Thread weist ziemlich stark darauf hin, dass ein Portal benötigt wird, das den Kontext, aber keine Ereignisse wurmlöchert. Stimmt das Kernteam zu? Wie auch immer, was ist hier der nächste Schritt?

Ich kann nicht wirklich verstehen, warum das Propagieren von Ereignissen aus dem Portal beabsichtigtes Verhalten ist? Dies steht völlig im Widerspruch zum Hauptgedanken der Vermehrung. Ich dachte, dass Portale genau deshalb erstellt wurden, um diese Art von Dingen zu vermeiden (wie manuelle Verschachtelung, Ereignisausbreitung usw.).

Ich kann bestätigen, dass, wenn Sie das Portal in der Nähe des Elementbaums platzieren, es Ereignisse propagiert:

class SomeComponent extends React.Component<any, any> {
  render() {
    return <>
      <div className="some-tree">
        // Portal here will bubble events
      </div>
      // Portal here will also bubble events, just checked
    </>
  }
}

+1 für diese Funktionsanfrage

In DOM sprudeln Ereignisse im DOM-Baum. In React sprudeln Ereignisse im Komponentenbaum.

Ich verlasse mich ziemlich stark auf das vorhandene Verhalten, ein Beispiel dafür sind Popouts, die verschachtelt sein können; das sind alles Portale, um Probleme mit overflow: hidden zu vermeiden, aber damit sich das Popout korrekt verhält, muss ich externe Klicks auf die Popout-Komponente erkennen (was sich von der Erkennung von Klicks außerhalb der gerenderten DOM-Elemente unterscheidet). . Vielleicht gibt es bessere Beispiele.

Ich denke, die robuste Diskussion hier hat deutlich gemacht, dass es gute Gründe für beide Verhaltensweisen gibt. Da createPortal eine React-Komponente innerhalb eines "Plain DOM"-Containerknotens rendert, glaube ich nicht, dass es für Reacts Synthetic Events praktikabel wäre, sich von einem Portal aus den Plain-Old-DOM-Baum hinauf zu verbreiten.

Da Portale schon lange nicht mehr verfügbar sind, ist es wahrscheinlich zu spät, das Standardverhalten auf "nicht über die Portalgrenze hinaus propagieren" zu ändern.

Basierend auf all den bisherigen Diskussionen ist mein einfachster Vorschlag dann (noch): Fügen Sie ein optionales Flag zu createPortal hinzu, das jede Ereignisausbreitung über die Portalgrenze hinaus verhindert.

Etwas robuster könnte die Möglichkeit sein, eine Whitelist von Ereignissen bereitzustellen, die die Grenze "durchdringen" dürfen, während der Rest gestoppt wird.

@gaearon Sind wir an dem Punkt, an dem das React-Team das tatsächlich übernehmen könnte? Dies ist ein Top-10-Thema, aber wir haben seit einiger Zeit nichts mehr von Ihnen gehört.

Ich möchte dem meine Unterstützung hinzufügen und stimme den Kommentaren von

Die Möglichkeit, Kontext von einem Ort im DOM zu einem anderen zu portieren, ist nützlich für die Implementierung aller Arten von Überlagerungen wie Tooltips, Dropdowns, Hovercards und Dialoge, bei denen der Inhalt der Überlagerung beschrieben wird durch und das Rendern im Kontext von der Auslöser. Da Kontext ein React-Konzept ist, löst dieser Mechanismus ein React-Problem. Auf der anderen Seite ist die Möglichkeit, DOM-Event-Bubbling von einer Stelle im DOM zu einer anderen zu portieren, ein ausgefallener Trick, mit dem Sie so tun können, als ob die DOM-Struktur anders wäre, als Sie sie explizit eingerichtet haben. Dies löst ein Problem bei der Verwendung von DOM-Ereignis-Bubbling für die Delegierung, wenn Sie an einen anderen Teil des DOM delegieren möchten. Wahrscheinlich sollten Sie sowieso Callbacks (oder Kontext) verwenden, wenn Sie über React verfügen, anstatt sich darauf zu verlassen, dass DOM-Ereignisse von innerhalb des Overlays nach außen sprudeln. Wie andere bereits erwähnt haben, möchten Sie selten "hineingreifen" und ein Ereignis verarbeiten, das absichtlich oder unabsichtlich innerhalb des Overlays stattfindet.

DOM-Ereignis-Bubbling löst in erster Linie ein Problem der Übereinstimmung von DOM-Ereignissen mit DOM-Zielen. Jeder Klick ist eigentlich ein Klick auf einen ganzen Satz verschachtelter Elemente. Es ist nicht am besten als ein hochrangiger Delegierungsmechanismus, IMO, zu denken, und die Verwendung von DOM-Ereignissen zum Delegieren über React-Komponentengrenzen hinweg ist keine großartige Kapselung, es sei denn, die Komponenten sind kleine private Hilfskomponenten, die zum Rendern vorhersehbarer DOM-Bits verwendet werden.

event.target === event.currentTarget hilft mir dieses Problem zu lösen. Aber das ist wirklich Kopfzerbrechen.

Das hat mich heute gebissen, als ich versucht habe, eine Popover-Komponente mit unstable_renderSubtreeIntoContainer zu migrieren, um createPortal . Die betreffende Komponente enthält ziehbare Elemente und wird als Nachkomme eines anderen ziehbaren Elements gerendert. Dies bedeutet, dass sowohl das übergeordnete Element als auch das Popover-Element Maus- und Touch-Ereignishandler enthalten, die beide bei der Interaktion mit dem Popover des Portals ausgelöst wurden.

Da unstable_renderSubtreeIntoContainer veraltet (?) ist, ist eine alternative Lösung erforderlich - keine der oben vorgestellten Problemumgehungen scheint tragfähige langfristige Lösungen zu sein.

Hey! Danke für all diese Vorschläge Jungs!
Es hat mir geholfen, eines meiner Probleme zu beheben.
Möchten Sie einen tollen und informativen Artikel über die Bedeutung und Fähigkeiten des React-Teams lesen? Ich denke, es wird für jeden nützlich sein, der an Entwicklung interessiert ist. Viel Glück!

IMO wünscht man sich häufiger, dass ein Portal Zugriff auf den Kontext bietet, aber keine Ereignisse aufbläst. Als wir Angular 1.x verwendeten, schrieben wir unseren eigenen Popup-Dienst, der $scope und eine Vorlagenzeichenfolge benötigte, diese Vorlage kompilierte/renderte und an den Text anhängte. Wir haben alle Popups/Modals/Dropdowns unserer Anwendung mit diesem Dienst implementiert, und wir haben das Fehlen von Event-Bubbling nicht ein einziges Mal übersehen.

Die stopPropagation() Problemumgehung scheint zu verhindern, dass native Ereignis-Listener auf window ausgelöst werden (in unserem Fall durch react-dnd-html5-backend hinzugefügt).

Hier ist eine minimale Reproduktion des Problems: https://codepen.io/mogel/pen/xxKRPbQ

Wenn es keinen Plan gibt, das synthetische Bubbling über Portale zu vermeiden, hat vielleicht jemand eine Problemumgehung, die das native Event-Bubbling nicht unterbricht?

Die Problemumgehung stopPropagation() scheint zu verhindern, dass native Ereignis-Listener im Fenster ausgelöst werden

Richtig. :(

Wenn es keinen Plan gibt, das synthetische Blubbern über Portale zu vermeiden

Trotz des Schweigens des Kernteams hoffe ich und viele andere in diesem Thread _wirklich_, dass es solche Pläne gibt.

Vielleicht hat jemand eine Problemumgehung, die das Blasen von nativen Ereignissen nicht unterbricht?

Die Problemumgehung meines Teams bestand darin, Portale ausschließlich wegen dieses eklatanten Problems zu verbieten. Wir präsentieren Bereiche mit einem Haken in einem Container, der innerhalb der anderen Kontexte der App lebt, sodass Sie Kontexte auf Stammebene kostenlos erhalten. alle anderen übergeben wir manuell. Nicht großartig, aber besser als sinnlose Whack-a-Mole-Event-Handler.

Seit der letzten Antwort eines Mitarbeiters aus dem Kernteam sind 17 Monate verschaffen :)

Die Problemumgehung meines Teams bestand darin, Portale ausschließlich wegen dieses eklatanten Problems zu verbieten. Wir präsentieren Bereiche mit einem Haken in einem Container, der innerhalb der anderen Kontexte der App lebt, sodass Sie Kontexte auf Stammebene kostenlos erhalten. alle anderen übergeben wir manuell. Nicht großartig, aber besser als sinnlose Whack-a-Mole-Event-Handler.

Ich kann mir keine generischen Ansätze vorstellen, um Kontext über Requisiten an das "Fake-Portal" weiterzugeben, ohne mich wieder auf kaskadierende Requisiten zu verlassen :(

Unzählig waren die Fehler, die ich auf https://github.com/reakit/reakit gefunden habe und die mit diesem Problem in Zusammenhang standen. Ich benutze React Portal oft und mir fällt kein einziger Fall ein, in dem ich Event-Blubbern von Portalen zu ihren übergeordneten Komponenten wollte.

Meine Problemumgehung bestand darin, sie entweder in meinen übergeordneten Ereignishandlern zu überprüfen:

event.currentTarget.contains(event.target);

Oder verwenden Sie stattdessen native Ereignisse:

const onClick = () => {};
React.useEffect(() => {
  ref.current.addEventListener("click", onClick);
  return () => ref.current.removeEventListener("click", onClick);
});

Ich verwende diese Ansätze intern in der Bibliothek. Aber keiner von ihnen ist ideal. Und da dies eine Open-Source-Komponentenbibliothek ist, kann ich nicht kontrollieren, wie die Leute Ereignishandler an die Komponenten übergeben.

Eine Option zum Deaktivieren des Ereignis-Bubblings würde all diese Probleme lösen.

Ich habe einen Semi-Workaround gehackt, der React-Bubbling blockiert und gleichzeitig einen Klon des Events auf window auslöst. Es scheint in Chrome, Firefox und Safari unter OSX zu funktionieren, aber IE11 wird ausgelassen, da event.target nicht manuell eingestellt werden kann. Bisher kümmert es sich nur um Maus-, Zeiger-, Tastatur- und Radereignisse. Ich bin mir nicht sicher, ob das Klonen von Drag-Ereignissen möglich wäre.

Leider kann es in unserer Codebasis nicht verwendet werden, da wir IE11-Unterstützung benötigen, aber vielleicht könnte jemand anderes es für seinen eigenen Gebrauch anpassen.

Was dies besonders verblüffend macht, ist, dass das 'Standard'-Verhalten den Komponentenbaum wieder _nach unten_ bläst. Nehmen Sie den folgenden Baum:

<Link>
   <Menu (portal)>
      <form onSubmit={...}>
         <button type="submit">

Ich ziehe mir seit Stunden die Haare aus, warum bei genau dieser Kombination von Komponenten das onSubmit meines Formulars nie aufgerufen wird – egal ob ich auf den Absenden-Button klicke oder die Eingabetaste in einem Eingabefeld innerhalb des Formulars drücke.

Schließlich habe ich festgestellt, dass es daran liegt, dass die Link Komponente von React Router eine onClick Implementierung hat, die e.preventDefault() tut, um das Neuladen des Browsers zu verhindern. Dies hat jedoch den unglücklichen Nebeneffekt, dass auch das Standardverhalten des Klickens auf die Schaltfläche zum Senden blockiert wird, das zufällig beim Senden des Formulars erfolgt. Was ich heute gelernt habe, ist, dass onSubmit tatsächlich vom Browser aufgerufen wird, als Standardaktion zum Drücken der Senden-Schaltfläche. Auch wenn Sie die Eingabetaste drücken, wird ein Klick auf den Absenden-Button und damit ein Absenden des Formulars ausgelöst.

Aber Sie sehen, wie die sprudelnde Reihenfolge der Ereignisse dies wirklich sehr seltsam macht.

  1. <input> [Eingabetaste drücken]
  2. <button type="submit"> [simulierter Klick]
  3. <Menu> [Ereignis verbreitet sich außerhalb des Portals]
  4. <Link> [Verbreitung erreicht Elternteil Link ]
  5. <Link> [ruft e.preventDefault() ]
  6. => Standard-Browser-Antwort auf das Klicken auf die Schaltfläche zum Senden wird abgebrochen
  7. => Formular wird nicht gesendet

Dies geschieht, obwohl wir die Schaltfläche und das Formular bereits im DOM übergeben haben und Link damit nichts zu tun hat und dieses Verhalten auch nicht blockieren wollte.

Die Lösung für mich (falls jemand auf das gleiche Problem stößt) war die häufig verwendete Lösung, den Inhalt von <Menu> in ein div mit onClick={e => e.stopPropagation()} . Aber mein Punkt ist, dass ich viel Zeit verloren habe, um das Problem aufzuspüren, weil das Verhalten wirklich nicht intuitiv ist.

Die Lösung für mich (falls jemand auf das gleiche Problem stößt) war die häufig verwendete Lösung, den Inhalt von <Menu> in ein div mit onClick={e => e.stopPropagation()} . Aber mein Punkt ist, dass ich viel Zeit verloren habe, um das Problem aufzuspüren, weil das Verhalten wirklich nicht intuitiv ist.

Ja – jede _einzelne Instanz des Problems_ hat die gleiche einfache Lösung, _sobald Sie den Fehler erfahren und richtig identifiziert haben_. Es ist eine sehr steile Grube des Scheiterns, die das React-Team hier geschaffen hat, und es ist frustrierend, keine Anerkennung von ihnen zu hören.

Ich habe mehrere Tage damit verbracht, ein anderes Problem zu debuggen, bei dem mouseenter unerwartet aus Portalen sprudelt. Selbst mit onMouseEnter={e => e.stopPropagation()} auf dem Div des Portals sprudeln die Ereignisse immer noch zum besitzenden Button, wie in https://github.com/facebook/react/issues/11387#issuecomment -340009465 (der erste Kommentar zu diesem Thema). mouseenter / mouseleave sollen gar nicht erst sprudeln...

Vielleicht noch seltsamer, wenn ich eine synthetische mouseenter Ereignisblase aus einem Portal zu einer Schaltfläche sehe, ist e.nativeEvent.type mouseout . React löst ein nicht sprudelndes synthetisches Ereignis basierend auf einem sprudelnden nativen Ereignis aus – und das obwohl stopPropagation für das synthetische Ereignis aufgerufen wird.

@gaearon @trueadm dieses Problem verursacht seit über zwei Jahren Könnte bitte jemand aus dem Team hier etwas beitragen?

In meinem Fall ließ das Öffnen der Window-Komponente durch Klicken auf eine Schaltfläche das Fenster verschwinden, da das Klicken auf Window einen Klick auf die Schaltfläche verursachte, der die Zustandsänderung verursachte

Ich bin neu bei React, ich benutze hauptsächlich jQuery und Vanillia JS, aber das ist ein umwerfender Fehler. Es kann etwa 1% der Fälle geben, in denen dieses Verhalten zu erwarten wäre...

Ich mag die beiden Lösungen von @diegohaz , aber ich denke immer noch, dass createPortal eine Option haben sollte, um das Sprudeln von Ereignissen zu stoppen.

Mein besonderer Anwendungsfall war, dass die onMouseLeave und onMouseEnter Handler eines Tooltips durch den Portalnachkommen des untergeordneten Elements ausgelöst wurden – was nicht erwünscht war. Native Ereignisse haben dies behoben, indem die Portal-Nachkommen ignoriert wurden, da sie keine Dom-Nachkommen sind.

+1 für die Option, das Sprudeln in Portalen zu stoppen. Es wurde vorgeschlagen, das Portal einfach als Geschwister (statt als Kind) der Komponente zu platzieren, aus der der Ereignis-Listener stammt, aber ich denke, das funktioniert in vielen Anwendungsfällen (einschließlich meiner) nicht.

Es sieht so aus, als ob ReactDOM.unstable_renderSubtreeIntoContainer entfernt wird , was bedeutet, dass es bald keine vernünftigen Problemumgehungen für dieses Problem mehr geben wird...

^ hilf uns @trueadm -nobi du bist unsere einzige Hoffnung

Es sieht so aus, als ob das Pingen auf GitHub nicht funktioniert 😞
Vielleicht könnte jemand mit einem aktiven Twitter-Account darüber twittern und einen der Mitwirkenden markieren?

Ich füge meine +1 für dieses Problem hinzu. Bei Notion verwenden wir derzeit eine benutzerdefinierte Portalimplementierung, die älter ist als React.createPortal , und wir leiten unsere Kontextanbieter manuell an den neuen Baum weiter. Ich habe versucht, React.createPortal zu adoptieren, wurde aber durch das unerwartete sprudelnde Verhalten blockiert:

Der Vorschlag von <Portal> außerhalb der <MenuItem> Komponente zu verschieben, um ein Geschwister zu werden, löst das Problem nur für eine einzelne Verschachtelungsebene. Das Problem bleibt bestehen, wenn Sie mehrere verschachtelte (zB) Menüelemente haben, die Untermenüs ausgliedern.

Dieses Problem wurde automatisch als veraltet markiert. Wenn Sie weiterhin von diesem Problem betroffen sind, hinterlassen Sie bitte einen Kommentar (z. B. "Beule"), und wir werden es offen halten. Es tut uns leid, dass wir es noch nicht priorisieren konnten. Wenn Sie neue zusätzliche Informationen haben, fügen Sie diese bitte Ihrem Kommentar bei!

Stoßen.

Dan hat einen Kommentar zu einem verwandten Problem hinterlassen:

@mogelbrod Dem habe ich derzeit nichts hinzuzufügen, aber so etwas ( #11387 (Kommentar) ) erscheint mir vernünftig, wenn Sie eine vorhandene Komponente migrieren.

Follow-up von Dan in derselben Ausgabe :

Danke für den Kontext zur Problemumgehung. Da Sie bereits über diese Domänenkenntnisse verfügen, ist der beste nächste Schritt wahrscheinlich, einen RFC für das gewünschte Verhalten und die von Ihnen in Betracht gezogenen Alternativen zu schreiben:

Unabhängig davon wird unstable_renderSubtreeIntoContainer nicht unterstützt, also entwirren wir diese beiden Diskussionen. Wir werden keine Context-Propagierung hinzufügen, da die gesamte API eingefroren ist und nicht aktualisiert wird.

Wir sollten auf jeden Fall einen React RFC veröffentlichen, um das Hinzufügen des diskutierten Flags oder vielleicht eine andere Lösung vorzuschlagen. Hat jemand besonderes Interesse daran , einen zu entwerfen (vielleicht @justjake , @craigkovatch oder @jquense)? Wenn nicht, werde ich sehen, was mir einfällt!

Obwohl ich daran interessiert bin, diese API weiterzuentwickeln, habe ich kein Interesse daran, einen RFC zu entwerfen. Meistens, weil es eine Menge Arbeit ist und es fast keine Chance gibt, dass es akzeptiert wird. Ich glaube nicht, dass das Kernteam tatsächlich RFCs in Betracht zieht, die noch nicht auf ihrer Roadmap stehen.

@jquense Ich glaube nicht, dass das richtig ist. Ja, es ist unwahrscheinlich, dass wir einen RFC zusammenführen, der nicht mit der Vision übereinstimmt, da das Hinzufügen einer neuen API immer bereichsübergreifend ist und alle anderen geplanten Funktionen beeinflusst. Und es ist fair, dass wir nicht oft kommentieren, was nicht funktioniert. Allerdings haben wir durch sie zu lesen, und vor allem , wenn wir nähern wir ein Thema , dass das Ökosystem in mehr Know - how hat. Als Beispiele https://github.com/reactjs/rfcs/pull/38 , https://github.com/ Reactjs/rfcs/pull/150 , https://github.com/reactjs/rfcs/pull/118 , https://github.com/reactjs/rfcs/pull/109 , https://github.com/reactjs/ rfcs/pull/32 haben alle unser Denken beeinflusst, auch wenn wir sie nicht explizit kommentiert haben.

Mit anderen Worten, wir nähern uns RFCs teilweise als Gemeinschaftsforschungsmechanismus. Dieser Kommentar von @mogelbrod (https://github.com/facebook/react/issues/16721#issuecomment-674748100), warum die

@gaearon Mein Kommentar soll nicht andeuten, dass das Team nicht auf

Ich bin sehr froh zu hören, dass Sie sich die anderen RFCs ansehen und dass sie zum Design für Funktionen beitragen, aber "Wir wurden von diesen externen RFCs beeinflusst, obwohl wir sie nie kommentiert haben", denke ich, veranschaulicht meinen Standpunkt, nicht fordern Sie es heraus.

Mit anderen Worten, wir nähern uns RFCs teilweise als Gemeinschaftsforschungsmechanismus.

Das ist super vernünftig, aber nicht das, was das RFC-Repo sagt, dass _sein_ Ansatz ist, und nicht, wie andere Leute im Allgemeinen über RFCs denken. Der RFC-Prozess ist in der Regel Bindeglied und Kommunikationspunkt zwischen Team und Community sowie in Bezug auf Feature-Betrachtung und -Prozess so etwas wie ein ausgeglichenes Spielfeld.

Größere Punkte zur Community Governance beiseite. Die Leute zu bitten, Zeit damit zu verbringen, detaillierte Vorschläge zu schreiben und sie dann gegenüber anderen externen Teilnehmern zu verteidigen, während das Reaktionsteam mit Schweigen konfrontiert wird, ist entmutigend und verstärkt aktiv den Eindruck, dass FB sich nur um seine eigenen Bedürfnisse nach OSS kümmert. Was stinkt, weil ich weiß, dass du nicht so fühlen oder designen wirst.

Wenn der RFC-Prozess lauten soll: "Hier können Sie Ihre Anliegen und Anwendungsfälle skizzieren und wir werden sie lesen, wann/wenn wir einen Punkt erreichen, an dem wir dieses Feature implementieren können". Ehrlich gesagt ist das ein guter Ansatz. Ich denke, die Community würde davon profitieren, dass dies ausdrücklich erwähnt wird, sonst wird (und tut) ppl das gleiche Maß an Beteiligung und Beteiligung, das andere RFC-Prozesse oft haben, und wird dann aktiv entmutigt, wenn dies nicht funktioniert. Ich habe diesen Eindruck sicherlich auch mit etwas mehr Einsicht als andere Mitwirkende.

Klar, ich glaube, ich stimme dem alles zu. Ich möchte dies nicht in einen Meta-Thread verwandeln, sondern nur sagen, dass, da die Leute immer wieder auf diesen Thread pingen , der beide Bedenken berücksichtigt Seiten berücksichtigt und ein tiefes Verständnis des Problemraums erarbeitet . Ich verstehe total, wenn die Leute sich nicht darauf einlassen möchten (teilweise basierend darauf, wie wir auf RFCs reagieren), weshalb ich es nicht früher vorgeschlagen habe – aber da ich immer wieder persönliche Pings bekomme, wollte ich das vorschlagen als Option für jemanden, der motiviert ist.

fair genug, dies ist nicht der richtige Ort, um Meta zu RFCs zu bekommen :)

@gaearon Dies ist die sechsthäufigste Ausgabe, die derzeit auf React geöffnet ist, und die vierthäufigste kommentierte Ausgabe. Es ist seit der Veröffentlichung von React 16 geöffnet und ist nur noch 2 Monate davon entfernt, jetzt 3 Jahre alt zu sein. Das React-Kernteam hat jedoch nur sehr wenig Engagement gezeigt. Es fühlt sich sehr ablehnend an zu sagen "es liegt an der Community, eine Lösung vorzuschlagen", nachdem so viel Zeit und Schmerz vergangen sind und aufgetreten sind. Bitte beachten Sie, dass dieses Verhalten, das standardmäßig eingestellt ist, ein Konstruktionsfehler war, obwohl es einige sehr nützliche Anwendungen hat. Es sollte nicht an der Community liegen, das RFC zu reparieren.

Ich bedauere, zu diesem Thema einen Kommentar abgegeben zu haben, und ziehe meinen Vorschlag zum Community-RFC zurück. Du hast Recht, wahrscheinlich eine schlechte Idee. Ich muss hinzufügen, dass dieses Thema sehr emotional aufgeladen ist und es mir als Mensch persönlich schwer fällt, mich damit auseinanderzusetzen – obwohl ich verstehe, dass es wichtig ist und viele Menschen ein starkes Gefühl dafür haben.

Lassen Sie mich kurz auf den Stand dieses Threads antworten.

Zuerst möchte ich mich bei den Leuten entschuldigen, die Kommentare abgegeben haben und die frustriert waren, dass wir die Follow-ups in diesem Thread nicht fortsetzten. Wenn ich diese Ausgabe von außen gelesen hätte, hätte ich wahrscheinlich den Eindruck, dass das React-Team einen Fehler gemacht hat, es nicht zugeben will und bereit ist, sich an einer einfachen Lösung festzusetzen ("nur einen Boolean hinzufügen, wie schwer kann es sein it be!") seit über zwei Jahren, weil ihnen die Gemeinschaft egal ist. Ich kann absolut verstehen, wie Sie zu diesem Schluss kommen.

Ich weiß, dass dieses Thema sehr hoch bewertet wird. Dies wurde in diesem Thread mehrmals angesprochen, vielleicht aus der Perspektive, dass wir, wenn das React-Team gewusst hätte, dass dies ein großer Schmerzpunkt ist, früher darauf eingegangen wären. Wir wissen, dass dies ein Problem ist – Leute schreiben uns regelmäßig privat darüber oder halten es als Beispiel dafür, dass das React-Team sich nicht um die Community kümmert. Obwohl ich voll und ganz anerkenne, dass die Stille frustrierend war, hat es der zunehmende Druck, "einfach etwas zu tun", es schwieriger gemacht, sich produktiv mit diesem Thema auseinanderzusetzen.

Für dieses Problem gibt es Problemumgehungen – was es im Gegensatz zu einer Sicherheitslücke oder einem Absturz macht, die dringend behoben werden müssen. Wir wissen, dass Wokarounds funktionieren (aber nicht ideal sind und nervig sein können), weil wir einige von ihnen selbst verwenden, insbesondere um Code herum, der vor React 16 geschrieben wurde. Ich denke, wir können uns einig sein, dass dieses Problem zweifellos sehr frustrierend war Anzahl von Personen, es gehört immer noch zu einer anderen Klasse von Problemen als ein Absturz oder ein Sicherheitsproblem, auf das innerhalb eines konkreten Zeitrahmens reagiert werden muss.

Darüber hinaus stimme ich der Auffassung nicht zu, dass es eine einfache Lösung gibt, die wir morgen implementieren können. Selbst wenn wir das anfängliche Verhalten als Fehler betrachten (wobei ich nicht sicher bin, ob ich damit einverstanden bin), bedeutet dies, dass die Messlatte dafür, dass das nächste Verhalten die volle Vielfalt von Anwendungsfällen handhabt, noch höher liegt . Wenn wir einige Fälle beheben, andere jedoch beheben, haben wir keine Fortschritte gemacht und eine Menge Abwanderung erzeugt. Beachten Sie, dass wir in dieser Ausgabe nichts über die Fälle erfahren, in denen das aktuelle Verhalten gut funktioniert. Wir werden erst davon hören, wenn wir es kaputt gemacht haben.

Um Ihnen ein Beispiel zu geben, ist das aktuelle Verhalten tatsächlich sehr hilfreich für den deklarativ fokussierten Management-Anwendungsfall, den wir seit einiger Zeit erforschen. Es ist nützlich, Fokus/Unschärfe als "innerhalb" eines Modals in Bezug auf den Teilebaum zu behandeln, obwohl es sich um ein Portal handelt. Wenn wir den in diesem Thread vorgeschlagenen "einfachen" createPortal(tree, boolean) Vorschlag versenden würden, würde dieser Anwendungsfall nicht funktionieren, da das Portal selbst nicht "wissen" kann, welches Verhalten wir wollen. Jede Untersuchung einer möglichen Lösung muss Dutzende von Anwendungsfällen berücksichtigen, von denen einige noch nicht einmal vollständig verstanden sind. Dies ist sicherlich irgendwann notwendig, aber es ist auch ein enormer Zeitaufwand, um es richtig zu machen, und bisher konnten wir uns nicht darauf konzentrieren.

Vor allem Veranstaltungen sind ein heikles Gebiet, zB haben wir gerade eine Reihe von Änderungen vorgenommen, die sich mit den Problemen der letzten Jahre befassen, und dies war dieses Jahr ein großer Schwerpunkt. Aber wir können nur so viele Dinge gleichzeitig tun.

Im Allgemeinen versuchen wir als Team, uns auf wenige Probleme tiefgreifend zu konzentrieren, anstatt auf viele Probleme oberflächlich. Leider bedeutet dies, dass einige konzeptionelle Mängel und Lücken möglicherweise jahrelang nicht geschlossen werden, weil wir gerade dabei sind, andere wichtige Lücken zu schließen oder kein alternatives Design ausgearbeitet haben, das das Problem endgültig gelöst hätte. Ich weiß, es ist frustrierend, das zu hören, und es ist ein Teil der Gründe, warum ich mich von diesem Thread ferngehalten habe. Einige andere ähnliche Threads haben sich zu tieferen Erklärungen der Probleme und möglichen Lösungen entwickelt, die hilfreich sind, aber dieser hat sich meistens in eine Flut von "+1" und Vorschlägen für eine "einfache" Lösung verwandelt, weshalb es schwierig war sich sinnvoll damit auseinanderzusetzen.

Ich weiß, dass dies nicht die Antwort ist, die die Leute hören wollten, aber ich hoffe, es ist besser als gar keine Antwort.

Eine weitere erwähnenswerte Sache ist, dass einige der in diesem Thread beschriebenen Schwachstellen möglicherweise auf andere Weise gelöst wurden. Zum Beispiel:

Genauer gesagt: Das Aufrufen von stopPropagation für das synthetische Fokusereignis, um zu verhindern, dass es aus dem Portal sprudelt, bewirkt, dass stopPropagation auch für das native Fokusereignis in Reacts erfasstem Handler auf #document aufgerufen wird, was bedeutet, dass es es nicht zu einem anderen erfassten Handler auf . geschafft hat

React verwendet keine Capture-Phase mehr zum Emulieren von Blasen und hört auch nicht mehr auf Ereignisse im Dokument. Ohne die Frustration abzutun, wird es auf jeden Fall notwendig sein, alles bisher Gepostete im Lichte der anderen Änderungen neu zu bewerten.

Die nativen Ereignisse sprudeln immer noch, und da unser React-Code in einer hauptsächlich jQuery-Anwendung gehostet wird, ist der globale jQuery-keyDown-Handler eingeschaltet

bekommt immer noch das Ereignis.

In ähnlicher Weise wird React 17 Ereignisse an die Root- und Portal-Container anhängen (und an diesem Punkt tatsächlich die native Verbreitung stoppen), sodass ich auch eine Lösung erwarten würde.

Bezüglich der Punkte zur Entfernung von renderSubtreeIntoContainer . Der einzige Unterschied zu ReactDOM.render besteht buchstäblich darin, dass es den Legacy-Kontext propagiert. Da jede Version, die renderSubtreeIntoContainer nicht enthält, auch keinen Legacy-Kontext enthält, würde ReactDOM.render eine 100% identische Alternative bleiben. Dies löst natürlich nicht das allgemeinere Problem, aber ich denke, dass die Bedenken bezüglich renderSubtree speziell etwas fehl am Platz sind.

@gaearon

Bezüglich der Punkte zur Entfernung von renderSubtreeIntoContainer . Der einzige Unterschied zu ReactDOM.render besteht buchstäblich darin, dass es den Legacy-Kontext propagiert. Da jede Version, die renderSubtreeIntoContainer nicht enthält, auch keinen Legacy-Kontext enthält, würde ReactDOM.render eine 100% identische Alternative bleiben. Dies löst natürlich nicht das allgemeinere Problem, aber ich denke, dass die Bedenken bezüglich renderSubtree speziell etwas fehl am Platz sind.

Nun, da Sie es erwähnt haben, frage ich mich, ob der folgende Code eine gültige und sichere Implementierung für ein React-Portal ohne Event-Bubbling wäre:

function Portal({ children }) {
  const containerRef = React.useRef();

  React.useEffect(() => {
    const container = document.createElement("div");
    containerRef.current = container;
    document.body.appendChild(container);
    return () => {
      ReactDOM.unmountComponentAtNode(container);
      document.body.removeChild(container);
    };
  }, []);

  React.useEffect(() => {
    ReactDOM.render(children, containerRef.current);
  }, [children]);

  return null;
}

CodeSandbox mit einigen Tests: https://codesandbox.io/s/react-portal-with-reactdom-render-m22dj?file=/src/App.js

Es gibt immer noch ein Problem, wenn Modern Context nicht durchgelassen wird, aber dies ist kein neues Problem ( renderSubtree ist auch davon betroffen). Die Problemumgehung besteht darin, Ihren Baum mit einer Reihe von Kontextanbietern zu umgeben. Insgesamt ist es nicht ideal, Bäume zu verschachteln, daher würde ich nicht empfehlen, in anderen als älteren bestehenden Codeszenarien zu diesem Muster zu wechseln.

Nochmals vielen Dank für die Zuschreibung @gaearon!

Es hört sich so an, als ob das Zusammenfassen der Liste der defekten Fälle + Problemumgehungen (aktualisiert für React v17) für jemanden außerhalb des Kernteams am produktivsten wäre (korrigiere mich, wenn ich falsch liege!).

Ich bin in den kommenden Wochen überfordert, möchte aber so schnell wie möglich fertig werden. Wenn jemand anderes dies früher tun oder sich mit Ausschnitten einmischen kann (wie es

Es wäre definitiv nützlich, eine Liste von Fällen zusammenzufassen, obwohl ich sagen würde, dass sie nicht nur defekte Fälle enthalten muss, sondern auch die Fälle, in denen das aktuelle Verhalten sinnvoll ist.

Wenn es einen öffentlichen Bereich gibt, den ich hinzufügen möchte, füge ich gerne Anwendungsfälle aus unseren Apps und als Autor von UI-Bibliotheken hinzu. Im Allgemeinen stimme ich Dan zu, dass es zwar manchmal nervig ist, aber leicht zu umgehen ist. In Fällen, in denen Sie möchten, dass React sprudelt, ist es sehr schwierig, den Fall ohne die Hilfe von React abzudecken.

Es wäre definitiv nützlich, eine Liste von Fällen zusammenzufassen, obwohl ich sagen würde, dass sie nicht nur defekte Fälle enthalten muss, sondern auch die Fälle, in denen das aktuelle Verhalten sinnvoll ist.

Ich füge diese gerne ein, wenn mir jemand einen Open-Source-Code/extrahierten Code zeigen kann, der darauf basiert! Wie Sie bereits erwähnt haben, ist es eine kleine Herausforderung, dies zu finden, da an diesem Thema nur Personen beteiligt sind, die Probleme mit dem aktuellen Verhalten haben 😅

Wenn es einen öffentlichen Bereich gibt, den ich hinzufügen möchte, füge ich gerne Anwendungsfälle aus unseren Apps und als Autor von UI-Bibliotheken hinzu. Im Allgemeinen stimme ich Dan zu, dass es zwar manchmal nervig ist, aber leicht zu umgehen ist. In Fällen, in denen Sie möchten, dass React sprudelt, ist es sehr schwierig, den Fall ohne die Hilfe von React abzudecken.

Haben Sie einen bestimmten Platz im Sinn oder würde das Teilen einer Codesandbox (oder Jsfiddle usw.) pro Fall als Starter funktionieren? Ich kann versuchen, sie alle zusammenzustellen, sobald wir einige Fälle gesammelt haben.

Ich habe hier einen Thread gestartet: https://github.com/facebook/react/issues/19637. Konzentrieren wir uns auf praktische Beispiele, während dieses hier einer allgemeinen Diskussion dient.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen