Vue: Diskussion: Der beste Weg, ein HOC zu erstellen

Erstellt am 25. Juli 2017  ·  40Kommentare  ·  Quelle: vuejs/vue

Ausführung

2.4.2

Reproduktionslink

https://jsfiddle.net/o7yvL2jd/

Schritte zum Reproduzieren

Ich habe nach dem richtigen Weg gesucht, HoC mit vue.js zu implementieren. Habe aber kein passendes Beispiel gefunden.
Der folgende Link enthält bekannte HoC-Implementierungen. Hat aber nicht funktioniert wie erwartet.
https://jsfiddle.net/o7yvL2jd/

Wie kann ich HoC mit vue.js implementieren?
Ich möchte nur wissen, wie man HoC auf die Weise von require.js implementiert.

Was wird erwartet?

Sie sind HoCs, die einfach als Parameter übergebene Komponenten rendern.
HoCs, die Slots und Events enthalten, werden normal gerendert.

Was passiert eigentlich?

Das zu rendernde Element fehlt oder die gerenderte Reihenfolge weicht von der baseComponent ab.
Einige HoC-Implementierungen funktionieren nicht mit Event-Handlern.

discussion

Hilfreichster Kommentar

Okay, also habe ich herumgespielt, um es einfacher zu machen.

Schau mal hier: https://jsfiddle.net/Linusborg/j3wyz4d6/

Ich bin mit der API nicht zufrieden, da es sich um eine sehr grobe Ideenskizze handelt, aber sie kann alles, was sie können sollte.

import { createHOC, createRenderFn, normalizeSlots } from 'vue-hoc'
const Component = {
  props: ['message']
  template: `<p>{{message}}</p>`
}
const HOC = createHOC(Component)

createHOC kümmert sich um:

  • Kopieren der Requisiten aus der Komponente
  • Tauschen Sie $createElement mit dem vom übergeordneten Element aus, um die richtigen Slots aufzulösen.
  • einen Namen hinzufügen
  • Wenn kein zweites Argument angegeben wird (wie im obigen Beispiel), wird die Komponente gerendert und weitergegeben:

    • Requisiten

    • attr

    • Zuhörer

    • normale Schlitze

    • Scope-Slots

Das allein ist natürlich nicht sehr hilfreich. Der Spaß passiert also im zweiten Argument, das ein einfaches Komponentenobjekt ist.

Wenn Sie die Renderfunktion selbst schreiben möchten, können Sie dies natürlich tun. Wenn Sie nur Requisiten, Attrs oder Listener erweitern möchten, können Sie den createRenderFn -Helfer verwenden. Es erstellt eine Renderfunktion wie die oben beschriebene Standardfunktion, führt jedoch alle attrs , props oder listeners , die Sie ihm übergeben, mit denen des übergeordneten Elements zusammen.

const HOC = createHOC(Component, {
  mounted() { 
    console.log(`lifecycle hooks work, it's simply component options`) 
  },
  render: createRenderFn(Component, {
    props: {
      message: 'Hello from your HOC!'
    }
  }
})

Wenn Sie Ihre eigene Renderfunktion schreiben möchten, können Sie den Helfer normalizeSlots verwenden, um das Objekt von this.$slots in ein geeignetes Array zur Weitergabe umzuwandeln:

const HOC = createHOC(Component, {
  render(h) {
    return h(Component, {
      props: { message: 'hi from HOC'}, // nothing from parent will be passed on
    }, normalizeSlots(this.$slots))
  }
})

Kommentare erwünscht :)

Alle 40 Kommentare

Hallo @eu81273

Vielen Dank für Ihr Interesse an diesem Projekt.

Bei Ihrem Problem handelt es sich jedoch um eine Nutzungs-/Supportfrage, und der Issue-Tracker ist ausschließlich für Fehlerberichte und Funktionsanfragen reserviert (wie in unserem Contributing Guide beschrieben).

Wir empfehlen Ihnen, im Forum , Stack Overflow oder in unserem Discord-Chat danach zu fragen, und helfen Ihnen gerne weiter.

FWIW, ich habe aus persönlichem Interesse nachgeschaut - das sollte in 100% der Fälle funktionieren.

const HOC = WrappedComponent => ({
  props: typeof WrappedComponent === 'function' // accept both a construtor and an options object
    ? WrappedComponent.options.props 
    : WrappedComponent.props,
  render (h) {
    // reduce all slots to a single array again.
    const slots = Object.keys(this.$slots).reduce((arr, key) => arr.concat(this.$slots[key]), []);
    return h(WrappedComponent, {
      attrs: this.$attrs,
      props: this.$props,
      on: this.$listeners,
    }, slots);
 }
});

Ich habe HOC04 in Ihrem Beispiel bearbeitet, da es der Lösung am nächsten kam:

https://jsfiddle.net/Linusborg/o7yvL2jd/22/

Bearbeiten: immer noch ein Problem mit Slots, Untersuchung ...

Ich könnte es gelöst haben: https://jsfiddle.net/BogdanL/ucpz8ph4/. Slots sind jetzt nur fest codiert, aber das ist trivial zu lösen.

Scheint, dass die Lösung entlang der Methode von @lbogdan liegt , aber createElement sollte eine Möglichkeit haben, Slots zu nehmen, genau wie es scopedSlots nehmen kann.

Allerdings ist es _noch_ viel Aufwand, ein HoC zu erstellen. Es gibt viel zu beachten, um durchzugehen, während Sie mit React nur die WrappedComponent mit Requisiten rendern.

Ich dachte nur an eine sehr einfache Lösung ... lassen Sie mich wissen, wenn ich hier etwas vermisse:

const HOC06 = WrappedComponent => Vue.extend({
   mounted () {
      console.log('mounted 6')
   },
   ...WrappedComponent
})

Basierend auf den Beispielen von @LinusBorg und @lbogdan ist die minimalste HoC-Implementierung, die Komponenten mit Steckplätzen verarbeiten kann,:

const HoC = WrappedComponent => ({
    props: typeof WrappedComponent === 'function' 
        ? WrappedComponent.options.props 
        : WrappedComponent.props,
    render (h) {
        const slots = this.$slots;
        const scopedSlots = {};
        Object.keys(slots).map(key => (scopedSlots[key] = () => slots[key]));

        return h(WrappedComponent, {
            attrs: this.$attrs,
            props: this.$props,
            on: this.$listeners,
            scopedSlots,
        });
     }
});

Wie @blocka erwähnt hat, ist es immer noch sehr aufwendig, ein HoC mit vue.js zu erstellen.

@eu81273

const HOC06 = WrappedComponent => Vue.extend({
   mounted () {
      console.log('mounted 6')
   },
   ...WrappedComponent
})

Das scheint deinen Test zu bestehen, oder? Natürlich müssten Sie es anpassen, je nachdem, ob WrappedComponent ein Konstruktor oder ein Objekt ist, aber es müssen keine Slots, Ereignisse oder Props übergeben werden.

es ist immer noch viel Aufwand, ein HoC mit vue.js zu erstellen.

Abgesehen von dem Problem mit Slots liegt dies nur daran, dass Vue eine komplexere API als React hat, was in diesem Szenario ein Nachteil ist. Ich bewundere die minimale API von Reacts in solchen Anwendungsfällen – Vue wurde nur mit etwas anderen Gestaltungszielen entworfen, sodass HOCs nicht so einfach kommen wie in React.

Aber es sollte ziemlich trivial sein, eine createHOC() -Hilfsfunktion zu erstellen, die diese anfängliche Einrichtung für Sie umschließt, nicht wahr?

Nun, es kommt wirklich darauf an, was das Endziel ist. Soweit ich weiß, besteht das Ziel von HoC darin, die ursprüngliche Komponente (WrappedComponent) irgendwie zu ändern (zu dekorieren), um Requisiten, Methoden, Ereignis-Listener usw. hinzuzufügen (oder einzufügen) (ähnlich wie ein Mixin, wirklich :smile: ). Die Variante HOC06 ändert nur die Komponentendefinition, sie ändert nicht die Art und Weise, wie sie instanziiert wird.

@blocka Das Ziel von HOCs ist es oft, den Zustand (z. B. von redux / vuex) abzurufen und ihn in die Requisiten der umschlossenen Komponente einzufügen - das würde mit Ihrem Ansatz nicht funktionieren.

@LinusBorg richtig. Ich wusste, dass es zu schön war, um wahr zu sein, und dass ich etwas Offensichtliches vergessen hatte.

Ich denke, dies ist ein gutes Beispiel für die Implementierung eines echten Anwendungsfalls HoC in Vue: https://github.com/ktsn/vuex-connect.

Vue Hocs wäre ein tolles Plus (da es fast immer in jeder Vue vs React-Debatte angesprochen wird). Vielleicht könnte ein offizielles Repo erstellt werden, um ein Vue-hoc-Creator-Paket zu entwickeln? Auf diese Weise konnten wir an einer robusten, unterstützten Implementierung arbeiten

Übrigens, ich habe einen besseren Weg gefunden: Verwenden Sie $createElement aus der übergeordneten Komponente anstelle der eigenen des HOC - dadurch löst das untergeordnete Element die Slots korrekt auf:

https://jsfiddle.net/o7yvL2jd/23/

Süß, aber umso mehr Grund, dass es ein offizielles Tool geben sollte, also wir
nicht alle erfinden diesen Code immer wieder neu.

Am So, 30. Juli 2017 um 16:33 Uhr, Thorsten Lünborg [email protected]
schrieb:

Übrigens, ich habe einen besseren Weg: Verwenden Sie $createElement aus der übergeordneten Komponente
anstelle der eigenen des HOC - dies lässt das Kind die Slots richtig auflösen:

https://jsfiddle.net/o7yvL2jd/23/


Sie erhalten dies, weil Sie erwähnt wurden.
Antworten Sie direkt auf diese E-Mail und zeigen Sie sie auf GitHub an
https://github.com/vuejs/vue/issues/6201#issuecomment-318927628 , oder stumm
der Faden
https://github.com/notifications/unsubscribe-auth/AACoury4Fix2jsX_lyTsS6CYOiHJaOuVks5sTOiugaJpZM4Oh0Ij
.

Es tut mir leid, dass ich noch keine offizielle Lösung gefunden habe. Freut mich aber, dass du es süß findest.

Meine Lösung ist auch nicht perfekt, es gibt noch andere Probleme zu lösen - dh Scoped Slots funktionieren nicht mit meinem neuesten Trick.

edit: oh egal, sie funktionieren

Eine offizielle Lösung wird es wahrscheinlich geben, zumindest würde ich das erwarten - aber sie muss gründlich durchdacht und getestet werden.

Okay, also habe ich herumgespielt, um es einfacher zu machen.

Schau mal hier: https://jsfiddle.net/Linusborg/j3wyz4d6/

Ich bin mit der API nicht zufrieden, da es sich um eine sehr grobe Ideenskizze handelt, aber sie kann alles, was sie können sollte.

import { createHOC, createRenderFn, normalizeSlots } from 'vue-hoc'
const Component = {
  props: ['message']
  template: `<p>{{message}}</p>`
}
const HOC = createHOC(Component)

createHOC kümmert sich um:

  • Kopieren der Requisiten aus der Komponente
  • Tauschen Sie $createElement mit dem vom übergeordneten Element aus, um die richtigen Slots aufzulösen.
  • einen Namen hinzufügen
  • Wenn kein zweites Argument angegeben wird (wie im obigen Beispiel), wird die Komponente gerendert und weitergegeben:

    • Requisiten

    • attr

    • Zuhörer

    • normale Schlitze

    • Scope-Slots

Das allein ist natürlich nicht sehr hilfreich. Der Spaß passiert also im zweiten Argument, das ein einfaches Komponentenobjekt ist.

Wenn Sie die Renderfunktion selbst schreiben möchten, können Sie dies natürlich tun. Wenn Sie nur Requisiten, Attrs oder Listener erweitern möchten, können Sie den createRenderFn -Helfer verwenden. Es erstellt eine Renderfunktion wie die oben beschriebene Standardfunktion, führt jedoch alle attrs , props oder listeners , die Sie ihm übergeben, mit denen des übergeordneten Elements zusammen.

const HOC = createHOC(Component, {
  mounted() { 
    console.log(`lifecycle hooks work, it's simply component options`) 
  },
  render: createRenderFn(Component, {
    props: {
      message: 'Hello from your HOC!'
    }
  }
})

Wenn Sie Ihre eigene Renderfunktion schreiben möchten, können Sie den Helfer normalizeSlots verwenden, um das Objekt von this.$slots in ein geeignetes Array zur Weitergabe umzuwandeln:

const HOC = createHOC(Component, {
  render(h) {
    return h(Component, {
      props: { message: 'hi from HOC'}, // nothing from parent will be passed on
    }, normalizeSlots(this.$slots))
  }
})

Kommentare erwünscht :)

@LinusBorg Sehr schön!

Was meiner Meinung nach helfen würde, wäre, sich reale HoC-Anwendungsfälle auszudenken und sie mit diesen Primitives zu lösen.

Absolut.

Ich erwähne dieses Problem (EDIT (mybad): https://github.com/vuejs/vuejs.org/issues/658).
Da Sie die undokumentierte $createElement-API verwenden, wäre es sinnvoll, sie für Plugin-Entwickler zu dokumentieren.

Ihr Link ist falsch (es sei denn, Sie wollten wirklich auf eine Ausgabe von 2014 verlinken)

Aber ja, technisch gesehen fehlt die $ceateElement API immer noch auf der API-Seite.

@AlexandreBonaventure Dieses Problem ist von vue 0.x Tagen. :Lächeln:
Außerdem ist createElement hier dokumentiert: https://vuejs.org/v2/guide/render-function.html#createElement -Arguments .

Die Funktion ist als Argument der Render-Funktion dokumentiert, aber nicht, dass sie über this.$createElement verfügbar ist. Dazu gibt es ein offenes Problem auf vuejs/vuejs.org/issues/658

@LinusBorg Aber es ist im Grunde dieselbe Funktion, die an die Funktion render() gesendet wird, richtig?

genauso. Es ist nur nicht klar dokumentiert, dass es über diese Instanzmethode auch außerhalb der Renderfunktion verfügbar ist.

Ich spiele gerade mit dem obigen Beispiel und es gibt ein paar Probleme, wenn es in einem komplexeren Szenario verwendet wird, daher ist ein offizielles Repo erforderlich. Ein paar Anmerkungen:

  • createRenderFn sollte prüfen, ob attrs/props/listeners Funktionen sind und sie auswerten, dies würde es Ihnen ermöglichen, Props usw. basierend auf vorhandenen Props dynamisch zu setzen.
  • für die Zusammensetzbarkeit sollte die Komponente der zweite Parameter sein, und wenn die gesamte createHOC-Methode kuriert wäre, könnten wir problemlos mehrere Hoc-Ersteller miteinander verketten.
  • da der Hoc als Mixin hinzugefügt wird, wenn Sie versuchen, 2 Hocs miteinander zu verketten (dh withDefaultProps(withProps(component, {}), {}) , hat der zweite Hoc keinen Zugriff auf das Requisitenbeispiel des übergeordneten Elements

Hey Leute, ich habe versucht, eine Implementierung davon zu schreiben.
https://github.com/jackmellis/vue-hoc/tree/develop
Lassen Sie mich wissen, was Sie denken, und ich werde versuchen, es bald zu veröffentlichen. Ich denke auch darüber nach, ein Paket im Recompose-Stil zu schreiben.

Ein Recompose-Style-Paket wäre toll. Hätte echt was gebrauchen können
wie kürzlich bei withState.

Am Samstag, den 19. August 2017 um 5:50 Uhr schrieb Jack [email protected] :

Hey Leute, ich habe versucht, eine Implementierung davon zu schreiben.
https://github.com/jackmellis/vue-hoc/tree/develop
Lassen Sie mich wissen, was Sie denken, und ich werde versuchen, es bald zu veröffentlichen. ich bin auch
Denken Sie darüber nach, ein Paket im Recompose-Stil zu schreiben.


Sie erhalten dies, weil Sie erwähnt wurden.
Antworten Sie direkt auf diese E-Mail und zeigen Sie sie auf GitHub an
https://github.com/vuejs/vue/issues/6201#issuecomment-323513287 , oder stumm
der Faden
https://github.com/notifications/unsubscribe-auth/AACoumQWQMgpVeIvzJ3Ti8A4kLBD04Hhks5sZq_zgaJpZM4Oh0Ij
.

@jackmellis Toll, dass du hier eine Führung übernimmst :)

Ein paar Gedanken zu Ihrem Feedback, das Sie zuvor gegeben haben:

  • Ich denke, die Curry-Version ist ein großartiger Punkt und sollte in gewisser Weise die "Standardeinstellung" sein, da HOCs im Allgemeinen so gemacht werden, nicht wahr?
  • Guter Punkt über das Problem mit Mixins. Habt ihr schon eine Idee, wie man das abmildern kann? Ich habe im Moment keine, aber mein Bauchgefühl ist, dass dies in einer Kombination der von Ihnen erstellten Curry-Varianten und der Verwendung Vue.config.optionsMergeStrategies gemildert werden sollte

An die Namensgebung habe ich auch gedacht. Ich mag createRenderFn nicht, etwas wie renderComponentWith wäre aussagekräftiger und sinnvoller in einem Szenario, in dem wir es in einige andere Knoten einbetten:

render(h) {
  return h('DIV', {staticClass: 'some-styling' }, renderComponentWith({ props: {... } }))
}

  • Am Ende habe ich mich für beide entschieden. createHOC ist also zuerst eine Komponente ohne Curry, und dann gibt es eine Curry-Variante createHOCc . Das React-Ökosystem ist im Gegensatz zu Vue, das sich eher wie ein OOP-Framework verhält, sehr funktionsorientiert. Daher denke ich, dass es am besten ist, mit dem Rest von Vue konsistent zu bleiben, aber dennoch eine funktionale Alternative anzubieten.
  • Ich habe gerade etwas Code hinzugefügt, um damit umzugehen. Anstatt das ganze Hoc als Mixin zu haben, füge ich das Hoc und die Optionen manuell zusammen, indem ich die optionsMergeStrategies verwende. Das einzige Problem dabei ist, dass optionsMergeStrategies eine VM als letzten Parameter erwartet, aber ich mache das Zusammenführen vor der Instanziierung, also gibt es keine VM.
  • Ich bin sehr zufrieden mit createRenderFn , da es genau das tut. Aber je mehr ich das zusammenfasse, desto weniger denke ich, dass die Leute es direkt verwenden werden. Die allgemeine Verwendung wird etwa so sein:
const hoc = createHOC(
  Component,
  {
    created(){
      /* ... */
    }
  },
  {
    props: (props) => ({
      ...props,
      someProp: 'foo'
    })
  }
);

Vielleicht verstehe ich dein Beispiel nicht ganz?

Vielleicht verstehe ich dein Beispiel nicht ganz?

Nein, ich denke, Sie sind auf dem richtigen Weg, meine Gedanken gingen in eine ähnliche Richtung, als ich nach meiner letzten Antwort weiter darüber nachdachte.

In meinem ersten POC habe ich es eingefügt, damit die Leute zusätzliches Markup um die gerenderte Komponente hinzufügen können, aber das würde bedeuten, dass es nicht mehr wirklich ein HOC ist, da es auch die Benutzeroberfläche einbringen würde ...

Ja, ich denke, Sie versuchen, zusätzliche Inhalte zu rendern, Sie haben das HOC-Territorium verlassen und könnten genauso gut ein SFC erstellen, um damit umzugehen.

Ich habe gerade vue-hoc auf npm veröffentlicht!

Ich habe auch an vue-compose gearbeitet, was eine schnelle Dokumentationssitzung ist, die auch noch nicht fertig ist. Obwohl es der Neuzusammenstellung ähnlich ist, handhabt Vue viele clevere Dinge (wie Caching-Berechnungen und fördert die Verwendung von this ), sodass es eigentlich nicht ganz so komplexe (oder funktionale) Syntax benötigt.

extends funktioniert anders, kann aber verwendet werden, um ähnliche Ergebnisse zu erzielen, das stimmt.

Ich werde dies schließen, da ich denke, dass das @jackmellis- Projekt eine solide Grundlage bietet. Wir beabsichtigen nicht, eine Möglichkeit zum Erstellen von HOCs in den Kern aufzunehmen, hauptsächlich weil wir keinen großen Vorteil gegenüber Mixins / Extends sehen.

hauptsächlich, weil wir keinen großen Vorteil gegenüber Mixins / Extends sehen.

@LinusBorg React hat mixin bereits aufgegeben, HOC bringt viele Vorteile.

Dieser Artikel hat die Vorteile analysiert:

  • Der Vertrag zwischen einer Komponente und ihren Mixins ist implizit.
  • Wenn Sie mehr Mixins in einer einzelnen Komponente verwenden, beginnen sie zu kollidieren.
  • Mixins neigen dazu, Ihrer Komponente mehr Status hinzuzufügen, während Sie nach weniger streben sollten.
  • ....

Ich denke, das Vue-Team sollte erwägen, HoC sorgfältiger zu unterstützen ... obwohl es nicht einfach ist (es scheint, dass Vue nicht auf diese Weise konzipiert ist).

Ich bin nicht davon überzeugt, dass HoCs ein so überlegenes Konzept sind. Die potenziellen Konflikte des „impliziten Vertrags“ können beispielsweise auch bei HoCs auftreten.

Siehe dazu diesen Vortrag des Betreuers von React-router: https://www.youtube.com/watch?v=BcVAq3YFiuc

Davon abgesehen finde ich sie auch nicht schlecht , sie sind in vielen Situationen nützlich. Sie sind einfach nicht die Wunderwaffe, für die sie in der React-Welt gelobt werden, aber sie haben ihre eigenen Nachteile.

Wie aus der obigen Diskussion hervorgeht, ist die Implementierung von HoCs in Vue nicht so trivial wie in React, da die API und Funktionalität von Vue breiter ist und mehr Grenzfälle berücksichtigt werden müssen.

Wir können sicherlich darüber sprechen, wie wir dies verbessern können, solange es nicht erforderlich ist, irgendetwas in Vue kaputt zu machen - HoCs sind meiner Meinung nach keine bahnbrechende Änderung wert.

666

Die minimalste HoC-Implementierung, die Komponenten mit Slots verarbeiten kann, ist:

function hoc(WrappedComponent) {
  return {
    render(h) {
      return h(WrappedComponent, {
        on: this.$listeners,
        attrs:this.$attrs,
        scopedSlots: this.$scopedSlots,
      });
    },
  };
}

im Vergleich zu den Antworten oben,

  • props config wird nicht benötigt, die von this.$attrs an child übergeben werden könnte
  • zusätzliche Slots werden nicht benötigt. $scopedSlots enthalten Slots seit v2.6.0+

Ich habe ein Beispiel geschrieben, um es zu überprüfen

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen