Vue: Discussion : Meilleure façon de créer un HOC

Créé le 25 juil. 2017  ·  40Commentaires  ·  Source: vuejs/vue

Version

2.4.2

Lien de reproduction

https://jsfiddle.net/o7yvL2jd/

Étapes à reproduire

J'ai cherché la bonne façon d'implémenter HoC avec vue.js. Mais je n'ai pas trouvé d'exemple approprié.
Le lien ci-dessous est connu des implémentations HoC. Mais n'a pas fonctionné comme prévu.
https://jsfiddle.net/o7yvL2jd/

Comment puis-je implémenter HoC avec vue.js ?
Je veux juste savoir comment implémenter HoC à la manière de react.js.

Qu'est-ce qui est attendu ?

Ce sont des HoC qui rendent simplement les composants passés en tant que paramètres.
Les HoC contenant des emplacements et des événements s'afficheront normalement.

Que se passe-t-il réellement ?

L'élément à rendre est manquant ou l'ordre de rendu diffère de baseComponent.
Certaines implémentations HoC ne fonctionnent pas avec les gestionnaires d'événements.

discussion

Commentaire le plus utile

D'accord, j'ai donc joué avec un moyen de rendre cela plus facile.

Jetez un œil ici : https://jsfiddle.net/Linusborg/j3wyz4d6/

Je ne suis pas satisfait de l'API car c'est une esquisse d'idée très approximative, mais elle est capable de faire tout ce qu'elle devrait pouvoir faire.

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

createHOC s'occupera de :

  • copier les accessoires du composant
  • échanger $createElement celui du parent pour une résolution appropriée des emplacements.
  • ajouter un nom
  • si aucun deuxième argument n'est fourni (comme dans l'exemple ci-dessus), il affiche le composant et transmet :

    • accessoires

    • attrs

    • les auditeurs

    • fentes normales

    • emplacements délimités

Cela en soi n'est pas très utile, bien sûr. Ainsi, le plaisir se produit dans le deuxième argument qui est un simple objet Component.

Si vous souhaitez écrire la fonction de rendu par vous-même, vous le pouvez bien sûr. Si vous souhaitez simplement étendre les props, attrs ou listeners, vous pouvez utiliser l'assistant createRenderFn . il créera une fonction de rendu comme celle par défaut décrite ci-dessus mais fusionnera tout attrs , props ou listeners que vous lui passerez avec ceux du parent.

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

Si vous souhaitez écrire votre propre fonction de rendu, vous pouvez utiliser l'assistant normalizeSlots pour transformer l'objet de this.$slots en un tableau approprié à transmettre :

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

Commentaires recherchés :)

Tous les 40 commentaires

Bonjour @ eu81273

Merci de votre intérêt pour ce projet.

Cependant, votre problème est une question d'utilisation/d'assistance, et le suivi des problèmes est réservé exclusivement aux rapports de bogues et aux demandes de fonctionnalités (comme indiqué dans notre Guide de contribution ).

Nous vous encourageons à le demander sur le forum , Stack Overflow ou sur notre chat discord et nous sommes heureux de vous aider là-bas.

FWIW, j'ai jeté un coup d'œil par intérêt personnel - cela devrait fonctionner dans 100% des cas.

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

J'ai édité HOC04 dans votre exemple car c'était le plus proche de la solution :

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

Edit : toujours un problème avec les slots, enquête...

Je l'ai peut-être résolu : https://jsfiddle.net/BogdanL/ucpz8ph4/. Les machines à sous sont juste codées en dur maintenant, mais c'est trivial à résoudre.

Il semble que la solution suive la méthode de @lbogdan mais createElement devrait avoir un moyen de prendre des slots, tout comme il peut prendre des scopedSlots.

Cependant, c'est _toujours_ beaucoup d'efforts pour créer une HoC. Il y a beaucoup de choses à retenir pour passer, tandis qu'avec réagir, vous rendez simplement le WrappedComponent avec des accessoires.

Je viens de penser à une solution très simple ... faites-moi savoir s'il me manque quelque chose ici:

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

Sur la base des exemples donnés par @LinusBorg et @lbogdan , l'implémentation HoC la plus minimale pouvant gérer des composants avec des emplacements est :

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

Comme @blocka l'a mentionné, il reste encore beaucoup d'efforts pour créer un HoC avec vue.js.

@eu81273

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

Cela semble passer votre test, non ? Bien sûr, vous devrez l'ajuster selon que WrappedComponent est un constructeur ou un objet, mais pas besoin de passer des slots, des événements ou des accessoires.

c'est encore beaucoup d'efforts pour créer un HoC avec vue.js.

Outre le problème avec les machines à sous, cela est simplement dû au fait que Vue a une API plus complexe que React, ce qui dans ce scénario est un inconvénient. J'admire l'API minimale de Reacts dans ce genre de cas d'utilisation - Vue a juste été conçu avec des objectifs de conception légèrement différents, donc les HOC ne viennent pas aussi facilement que dans React.

Mais il devrait être assez simple de créer une fonction d'assistance createHOC() qui encapsule cette configuration initiale pour vous, n'est-ce pas ?

Eh bien, cela dépend vraiment de l'objectif final. D'après ce que j'ai compris, le but de HoC est en quelque sorte de changer (décorer) le composant d'origine (WrappedComponent) pour ajouter (ou injecter) des accessoires, des méthodes, des écouteurs d'événements, etc. (un peu comme un mixin, vraiment :smile: ). HOC06 variante

@blocka Le but des HOC est souvent d'obtenir l'état (par exemple à partir de redux / vuex) et de l'injecter dans les accessoires du composant enveloppé - cela ne fonctionnerait pas avec votre approche.

@LinusBorg à droite. Je savais que c'était trop beau pour être vrai et que j'oubliais quelque chose d'évident.

Je pense que c'est un bon exemple d'implémentation d'un cas d'utilisation réel HoC dans Vue : https://github.com/ktsn/vuex-connect.

Vue Hocs serait un plus formidable (puisqu'il est presque toujours évoqué dans tout débat vue vs réaction). Peut-être qu'un référentiel officiel pourrait être créé pour développer un package vue-hoc-creator ? De cette façon, nous pourrions travailler sur une mise en œuvre robuste et soutenue

Au fait, il y a un meilleur moyen : utilisez $createElement du composant parent au lieu de celui du HOC - cela permet à l'enfant de résoudre correctement les emplacements :

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

Mignon, mais raison de plus pour qu'il y ait un outil officiel donc nous
ne continuez pas tous à réinventer ce code.

Le dimanche 30 juillet 2017 à 16h33, Thorsten Lünborg [email protected]
a écrit:

Btw, a un meilleur moyen: utilisez $createElement du composant parent
au lieu de celui du HOC - cela permet à l'enfant de résoudre correctement les emplacements :

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


Vous recevez ceci parce que vous avez été mentionné.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/vuejs/vue/issues/6201#issuecomment-318927628 , ou muet
le fil
https://github.com/notifications/unsubscribe-auth/AACoury4Fix2jsX_lyTsS6CYOiHJaOuVks5sTOiugaJpZM4Oh0Ij
.

Je suis désolé de ne pas avoir encore trouvé de solution officielle. Content que tu trouves ça mignon quand même.

Ma solution n'est pas parfaite non plus, il y a d'autres problèmes à résoudre - c'est-à-dire que les emplacements à portée ne fonctionneront pas avec ma dernière astuce.

edit: oh, tant pis, ils fonctionnent

Une solution officielle sera probablement faite, du moins je m'y attendrais - mais elle doit être soigneusement réfléchie et testée.

D'accord, j'ai donc joué avec un moyen de rendre cela plus facile.

Jetez un œil ici : https://jsfiddle.net/Linusborg/j3wyz4d6/

Je ne suis pas satisfait de l'API car c'est une esquisse d'idée très approximative, mais elle est capable de faire tout ce qu'elle devrait pouvoir faire.

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

createHOC s'occupera de :

  • copier les accessoires du composant
  • échanger $createElement celui du parent pour une résolution appropriée des emplacements.
  • ajouter un nom
  • si aucun deuxième argument n'est fourni (comme dans l'exemple ci-dessus), il affiche le composant et transmet :

    • accessoires

    • attrs

    • les auditeurs

    • fentes normales

    • emplacements délimités

Cela en soi n'est pas très utile, bien sûr. Ainsi, le plaisir se produit dans le deuxième argument qui est un simple objet Component.

Si vous souhaitez écrire la fonction de rendu par vous-même, vous le pouvez bien sûr. Si vous souhaitez simplement étendre les props, attrs ou listeners, vous pouvez utiliser l'assistant createRenderFn . il créera une fonction de rendu comme celle par défaut décrite ci-dessus mais fusionnera tout attrs , props ou listeners que vous lui passerez avec ceux du parent.

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

Si vous souhaitez écrire votre propre fonction de rendu, vous pouvez utiliser l'assistant normalizeSlots pour transformer l'objet de this.$slots en un tableau approprié à transmettre :

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

Commentaires recherchés :)

@LinusBorg Très sympa !

Ce qui, à mon avis, aiderait, c'est de proposer des cas d'utilisation réels de HoC et de les résoudre à l'aide de ces primitives.

Absolument.

Je mentionne ce problème (EDIT (mybad): https://github.com/vuejs/vuejs.org/issues/658).
Puisque vous utilisez l'API $createElement non documentée, il vaudrait la peine de la documenter pour les développeurs de plugins.

Votre lien est erroné (sauf si vous vouliez vraiment créer un lien vers un numéro de 2014)

Mais oui, techniquement, l'API $ceateElement est toujours manquante sur la page API.

@AlexandreBonaventure Ce problème provient de la vue 0.x jours. :le sourire:
De plus, createElement est documenté ici : https://vuejs.org/v2/guide/render-function.html#createElement -Arguments .

La fonction est documentée en tant qu'argument de la fonction de rendu, mais pas qu'elle soit disponible via this.$createElement . Il y a un problème ouvert pour cela sur vuejs/vuejs.org/issues/658

@LinusBorg Mais c'est fondamentalement la même fonction qui est envoyée à la fonction render() , n'est-ce pas?

exactement le même. Il n'est tout simplement pas documenté clairement qu'il est également disponible en dehors de la fonction de rendu via cette méthode d'instance.

Je joue juste avec l'exemple ci-dessus et il y a quelques problèmes lors de son utilisation dans un scénario plus complexe, d'où la nécessité d'un dépôt officiel. Quelques remarques :

  • createRenderFn doit vérifier si attrs/props/listeners sont des fonctions et les évaluer, cela vous permettrait de définir dynamiquement des accessoires, etc. en fonction des accessoires existants.
  • pour la composabilité, le composant devrait être le deuxième paramètre, et si toute la méthode createHOC était curry, nous pourrions facilement enchaîner plusieurs créateurs hoc.
  • parce que le hoc est ajouté en tant que mixin, si vous essayez d'enchaîner 2 hocs ensemble (c'est-à-dire withDefaultProps(withProps(component, {}), {}) le deuxième hoc n'a pas accès à l' exemple d'accessoires du parent

Hé les gens, j'ai cherché à écrire une implémentation de ceci.
https://github.com/jackmellis/vue-hoc/tree/develop
Faites-moi savoir ce que vous en pensez et je chercherai à le publier bientôt. Je pense également à écrire un package de style recompose.

Un package de style de recomposition serait formidable. Vraiment aurait pu utiliser quelque chose
comme withState récemment.

Le samedi 19 août 2017 à 5h50, Jack [email protected] a écrit :

Hé les gens, j'ai cherché à écrire une implémentation de ceci.
https://github.com/jackmellis/vue-hoc/tree/develop
Faites-moi savoir ce que vous en pensez et je chercherai à le publier bientôt. je suis aussi
penser à écrire un package de style recompose.


Vous recevez ceci parce que vous avez été mentionné.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/vuejs/vue/issues/6201#issuecomment-323513287 , ou muet
le fil
https://github.com/notifications/unsubscribe-auth/AACoumQWQMgpVeIvzJ3Ti8A4kLBD04Hhks5sZq_zgaJpZM4Oh0Ij
.

@jackmellis Super que vous preniez les devants à ce sujet :)

Quelques réflexions sur vos commentaires que vous avez donnés précédemment :

  • Je pense que la version au curry est un excellent point et devrait être la "valeur par défaut" d'une certaine manière, car c'est ainsi que les HOC sont généralement réalisés, n'est-ce pas ?
  • Bon point sur le problème avec les mixins. Avez-vous déjà une idée de comment atténuer cela? Je n'en ai pas pour le moment, mais mon intuition est que cela devrait pouvoir être atténué en combinant les variantes au curry que vous avez créées et en utilisant Vue.config.optionsMergeStrategies

J'ai aussi pensé à nommer. Je n'aime pas createRenderFn , quelque chose comme renderComponentWith serait plus significatif et aurait plus de sens dans un scénario où nous l'intégrons dans d'autres nœuds :

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

  • Au final j'ai opté pour les deux. Donc createHOC est le premier composant non curry, puis il y a une variante curry createHOCc . L'écosystème React est très fonctionnel, contrairement à Vue, qui agit plus comme un framework OOP. Je pense donc qu'il est préférable de rester cohérent avec le reste de Vue, tout en offrant une alternative fonctionnelle.
  • Je viens d'ajouter du code pour gérer cela. Plutôt que d'avoir tout le hoc comme mixin, je fusionne manuellement le hoc et les options en utilisant les optionsMergeStrategies. Le seul problème avec cela est que optionsMergeStrategies attend un vm comme dernier paramètre, mais je fais la fusion avant l'instanciation, donc il n'y a pas de vm.
  • Je suis assez content de createRenderFn puisque c'est exactement ce qu'il fait. Mais plus je mets cela ensemble, moins je pense que les gens l'utiliseront directement. L'utilisation générale sera quelque chose comme:
const hoc = createHOC(
  Component,
  {
    created(){
      /* ... */
    }
  },
  {
    props: (props) => ({
      ...props,
      someProp: 'foo'
    })
  }
);

Peut-être que je ne comprends pas bien votre exemple?

Peut-être que je ne comprends pas bien votre exemple?

Non, je pense que vous êtes sur la bonne voie, mes pensées allaient dans le même sens lorsque j'y ai repensé après ma dernière réponse.

Dans mon POC initial, je l'ai inclus afin que les gens puissent ajouter un balisage supplémentaire autour du composant rendu, mais cela signifierait que ce n'est plus vraiment un HOC, car cela apporterait également une interface utilisateur ...

Oui, je pense que vous essayez de rendre du contenu supplémentaire, vous êtes sorti du territoire HOC et vous pourriez aussi bien créer un SFC pour le gérer.

Je viens de publier vue-hoc sur npm !

J'ai également travaillé sur vue-compose qui est une session de documentation rapide loin d'être également prête. Bien qu'il soit similaire à la recomposition, Vue gère beaucoup de choses intelligentes (comme la mise en cache des calculs et encourage l'utilisation de this ) donc il n'a pas besoin d'une syntaxe aussi complexe (ou de style fonctionnel).

extends fonctionne différemment, mais peut être utilisé pour obtenir des résultats similaires, c'est vrai.

Je vais fermer ceci car je pense que le projet @jackmellis fournit une base solide. Nous n'avons pas l'intention d'inclure un moyen de créer des HOC dans le noyau, principalement parce que nous ne voyons pas un grand avantage par rapport aux mixins/Extends.

principalement parce que nous ne voyons pas un grand avantage par rapport aux mixins/Extends.

@LinusBorg React a déjà abandonné mixin , HOC apporte de nombreux avantages.

Cet article a analysé les avantages :

  • Le contrat entre un composant et ses mixins est implicite.
  • Au fur et à mesure que vous utilisez plus de mixins dans un seul composant, ils commencent à se heurter.
  • Les mixins ont tendance à ajouter plus d'état à votre composant alors que vous devriez vous efforcer d'en avoir moins.
  • ....

Je pense que l'équipe Vue devrait envisager de prendre en charge HoC avec plus d'attention... bien que ce ne soit pas facile (il semble que Vue ne soit pas conçu de cette manière).

Je ne suis pas convaincu que les CdC soient un concept si supérieur. Les conflits potentiels de "contrat implicite" peuvent également se produire avec les HoC, par exemple.

Voir cette discussion Par le mainteneur de React-router à ce sujet : https://www.youtube.com/watch?v=BcVAq3YFiuc

Cela étant dit, je ne pense pas qu'ils soient mauvais non plus, ils sont utiles dans de nombreuses situations. Ils ne sont tout simplement pas la solution magique dont ils sont loués comme dans le monde React, cependant, ils ont leurs propres inconvénients.

Comme il ressort de la discussion ci-dessus, la mise en œuvre des HoC dans Vue n'est pas aussi triviale que dans React, car l'API et les fonctionnalités de Vue sont plus larges et davantage de cas extrêmes doivent être pris en charge.

Nous pouvons sûrement parler de la façon d'améliorer cela tant que cela ne nécessite pas de casser quoi que ce soit dans Vue - les HoC ne sont pas un changement radical à mon avis.

666

l'implémentation HoC la plus minimale qui peut gérer des composants avec des slots est :

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

comparant aux réponses ci-dessus,

  • props config n'est pas nécessaire, ce qui pourrait être passé de this.$attrs à child
  • les emplacements supplémentaires ne sont pas nécessaires, ceci. $scopedSlots contient des emplacements depuis la v2.6.0+

j'ai écrit un exemple pour vérifier

Cette page vous a été utile?
0 / 5 - 0 notes