Vue: API améliorée pour les composants de l'interface utilisateur, réduisant le passe-partout pour le transfert d'attributs et d'événements

Créé le 27 juin 2017  ·  46Commentaires  ·  Source: vuejs/vue

Quel problème cette fonctionnalité résout-elle ?

Il existe de nombreux cas où les attributs passés à un composant Vue ne doivent pas être ajoutés à l'élément racine, mais plutôt à un sous-élément. Par exemple, dans ce composant d'interface utilisateur , une quantité incroyable d'accessoires doit être utilisée pour garantir que les attributs sont ajoutés à l'élément input , au lieu du wrapper div .

De plus, il est souvent souhaitable d'exposer tous les écouteurs d'événement sur un élément de formulaire au parent, ce qui nécessite également beaucoup de passe-partout actuellement si l'élément n'est pas la racine (auquel cas, le modificateur .native peut résoudre le problème ).

A quoi ressemble l'API proposée ?

EDIT : Commencez ici pour rattraper le retard sur la discussion.

Actuellement, par défaut, l'élément "exposé" (celui auquel des attributs arbitraires peuvent être ajoutés) est toujours l'élément racine. Une nouvelle directive pourrait être utilisée pour définir un élément exposé différent. Quelques idées pour le nom de la directive :

  • v-expose (probablement mon préféré)
  • v-expose-attrs (probablement plus clair, mais plus long)
  • v-main
  • v-primary

Si v-expose est ajouté à un élément, il acceptera les attributs passés à son composant - et ces attributs ne seront __plus__ passés à l'élément racine.

D'autres fonctionnalités qui peuvent être intéressantes :

  • Si la directive est définie sur plusieurs éléments, les attributs seront dupliqués sur chacun d'eux
  • Dans le cas où un sous-ensemble d'attributs devrait être accepté par un élément, v-expose pourrait accepter une chaîne ou un tableau de chaînes (par exemple v-expose="class" ou v-expose="['class', 'type', 'placeholder']" ). Dans ce cas, ces attributs seraient ajoutés à l'élément (encore une fois, au lieu de l'élément racine), mais tous les autres attributs seraient ajoutés à l'élément racine ou au(x) élément(s) avec un v-expose .
discussion feature request

Commentaire le plus utile

@chrisvfritz comment cela fonctionnerait-il dans les fonctions de rendu ?

Je pense qu'il vaudrait peut-être mieux :

  • fournir une option pour désactiver l'héritage automatique des attributs pour le nœud racine
  • exposer ces attributs comme $attributes , par exemple (nommer à déterminer)
  • utilisez v-bind pour les ajouter où vous voulez, un peu comme nous l'avons déjà montré avec $props :
v-bind="$attributes"

Cela aurait l'avantage supplémentaire de fonctionner pratiquement à l'identique dans les fonctions JSX/rendu

Tous les 46 commentaires

Hmm, je ne sais pas à ce sujet, mais pour des composants comme celui-ci, je pense que vous pouvez utiliser JSX ou createElement pour diffuser des accessoires.

https://github.com/doximity/vue-genome

Pour nous, ce serait un grand. Nous enveloppons toutes les entrées dans une étiquette à des fins de style et d'UX. Je suis d'accord pour dire que nous pourrions plutôt passer à jsx, mais les modèles sont tellement plus faciles à suivre pour tout le monde.

@Austio , malheureusement, c'est la récompense pour les modèles... attendez... peut-être pourrions-nous penser à un moyen de spread props dans les modèles de vue ?

J'aime cette fonctionnalité personnellement. Mais cela semble rompre la cohérence du comportement de v-bind, comme parfois j'ai encore besoin de lier la propriété class pour l'élément racine.

Alors, que diriez-vous d'utiliser une paire de directives comme getter et setter comme :

À l'intérieur du composant, définissez une ancre v-expose :

<input v-expose="foo" />

Lors de son utilisation :

<the-component v-define:foo="{propA: '', propB: ''}"></the-component>

<!-- or maybe use v-bind for it directly -->
<the-component :foo="{propA: '', propB: ''}"></the-component>

@jkzing , ça a l'air génial, mais encore une fois, ça ressemble à un spread de base et avec des problèmes potentiels comme comment définiriez-vous @keyup.enter.prevent="myAction" ?

vous ne pouvez pas simplement <the-component :foo="{'@keyup.enter.prevent': myAction}"></the-component> , cela signifie que vous devrez conserver tous les modificateurs comme enter et prevent dans le runtime (qui fait partie de vue-template-compiler au m)

@nickmessing

qui ressemble à une tartinade de base

La chose dont nous parlons est d'apporter quelque chose comme la propagation pour les utilisateurs de modèles

<the-component :foo="{'@keyup.enter.prevent': myAction}"></the-component>

@ est un shortland v-on, ne signifie pas prop (v-bind).

@jkzing , dans le lien de la description, il y a aussi beaucoup de liaisons v-on

@nickmessing Um... Quant aux liaisons v-on , c'est un autre sujet IMO, comme le bouillonnement d'événements. ??

@jkzing , c'était tout le concept de v-expose afaik, pour que toutes les propriétés "aillent" à un certain élément du composant

@nickmessing , Je ne peux pas être sûr de la proposition originale, mais je ne pense pas qu'un écouteur d'événement devrait être considéré comme attribute .

@jkzing , probablement pas, mais compte tenu de l'exemple courant de <my-awesome-text-input /> où vous pouvez avoir > 9000 accessoires différents, vous voulez juste qu'ils vous parviennent tous <input /> qui se trouve à l'intérieur de votre composant personnalisé sans une tonne de code.

Personnellement, j'utilise v-bind="$props" ou vous pouvez les filtrer pour exclure les accessoires que vous ne souhaitez pas appliquer. De cette façon, vous pouvez appliquer plusieurs accessoires à la fois sur une entrée. En effet, v-expose peut être utile car pour les composants wrapper tels que les entrées, vous devez spécifier tous ces accessoires html

Donc ça
https://github.com/almino/semantic-ui-vue2/blob/master/src/elements/Input.vue#L9
canne être réduite à v-bind="$props" ou v-bind="filteredProps" où filteredProps pourrait être une propriété calculée

@cristijora Nous utilisons aussi v-bind="someProps" . Le problème avec cette solution est que des propriétés excessives seront ajoutées en tant qu'attributs HTML. Ce serait formidable si v-bind= pouvait filtrer toutes les propriétés qui ne sont pas acceptées par le composant. Avec des <component> dynamiques, nous ne savons pas quels accessoires filtrer dans la propriété calculée. Bien qu'il soit possible d'extraire options.props et d'utiliser lodash._pick .

Est-ce vraiment faisable avec une directive ?

@posva , je ne pense pas que cela fonctionnera comme une directive en soi, mais cela peut faire partie du moteur de modèle de vue qui fait quelque chose comme se propager en interne + une certaine propagation d'événements

@posva Pas une directive

@chrisvfritz avez-vous des idées sur une API pour savoir comment elle serait utilisée (en précisant quoi exposer et comment ajouter à l'enfant)

Je pouvais voir que cela était similaire à l'utilisation pour fournir/injecter le concept.

@Austio Je ne comprends peut-être pas la question, mais je donne quelques réflexions sur l'API dans le message d'origine.

Hé Chris, cela voulait dire des réflexions supplémentaires sur l'utilisation de similaire pour fournir un inject où vous déclarez ce qui doit être exposable chez le parent, puis en l'utilisant chez l'enfant.

Ah, je vois. Je ne suis pas sûr que cela soit nécessaire. Les informations peuvent déjà être transmises via des accessoires et des emplacements - et même les propriétés privées du parent sont accessibles avec this.$parent , bien que je pense qu'il est préférable d'éviter ce modèle.

@Austio Pensez -vous à un cas d'utilisation particulier ?

@chrisvfritz comment cela fonctionnerait-il dans les fonctions de rendu ?

Je pense qu'il vaudrait peut-être mieux :

  • fournir une option pour désactiver l'héritage automatique des attributs pour le nœud racine
  • exposer ces attributs comme $attributes , par exemple (nommer à déterminer)
  • utilisez v-bind pour les ajouter où vous voulez, un peu comme nous l'avons déjà montré avec $props :
v-bind="$attributes"

Cela aurait l'avantage supplémentaire de fonctionner pratiquement à l'identique dans les fonctions JSX/rendu

@LinusBorg J'aime votre façon de penser. 😄 Votre chemin est beaucoup plus intuitif.

En passant, je pense qu'avec cette API en place, la prochaine version majeure de Vue pourrait même supprimer complètement l'héritage automatique des attributs, de sorte que la communication entre les composants puisse rester explicite des deux côtés.

Il serait possible de déprécier ou de supprimer ce comportement, oui.

Si cela vaut la peine, les modifications éventuellement nécessaires sur de nombreux composants dans les bibliothèques, etc. doivent être décidées et devraient être discutées avec la communauté, en particulier les auteurs de collections d'interfaces utilisateur.

A propos de la fonctionnalité proposée : cette information est déjà disponible dans les composants fonctionnels via context.data.attributes , donc cette fonctionnalité donnerait fondamentalement la même fonctionnalité aux composants d'instance.

Oui, exactement. L'objectif principal que j'ai en tête est de simplifier le travail des auteurs de composants d'interface utilisateur (à la fois tiers et internes). Il y a actuellement beaucoup de cas où quelque chose comme ceci est nécessaire :

<input
  v-bind:id="id"
  v-bind:accept="accept"
  v-bind:alt="alt"
  v-bind:autocomplete="autocomplete"
  v-bind:autofocus="autofocus"
  v-bind:checked="checked"
  v-bind:dirname="dirname"
  v-bind:disabled="disabled"
  v-bind:form="form"
  v-bind:formaction="formaction"
  v-bind:formenctype="formenctype"
  v-bind:formmethod="formmethod"
  v-bind:formnovalidate="formnovalidate"
  v-bind:formtarget="formtarget"
  v-bind:list="list"
  v-bind:max="max"
  v-bind:maxlength="maxlength"
  v-bind:min="min"
  v-bind:multiple="multiple"
  v-bind:name="name"
  v-bind:pattern="pattern"
  v-bind:placeholder="placeholder"
  v-bind:readonly="readonly"
  v-bind:required="required"
  v-bind:src="src"
  v-bind:step="step"
  v-bind:type="type"
  v-bind:value="value"
  v-on:keydown="emitKeyDown"
  v-on:keypress="emitKeyPress"
  v-on:keyup="emitKeyUp"
  v-on:mouseenter="emitMouseEnter"
  v-on:mouseover="emitMouseOver"
  v-on:mousemove="emitMouseMove"
  v-on:mousedown="emitMouseDown"
  v-on:mouseup="emitMouseUp"
  v-on:click="emitClick"
  v-on:dblclick="emitDoubleClick"
  v-on:wheel="emitWheel"
  v-on:mouseleave="emitMouseLeave"
  v-on:mouseout="emitMouseOut"
  v-on:pointerlockchange="emitPointerLockChange"
  v-on:pointerlockerror="emitPointerLockError"
  v-on:blur="emitBlur"
  v-on:change="emitChange($event.target.value)"
  v-on:contextmenu="emitContextMenu"
  v-on:focus="emitFocus"
  v-on:input="emitInput($event.target.value)"
  v-on:invalid="emitInvalid"
  v-on:reset="emitReset"
  v-on:search="emitSearch"
  v-on:select="emitSelect"
  v-on:submit="emitSubmit"
>

Une nouvelle propriété $attributes pourrait la raccourcir à ceci :

<input
  v-bind="$attributes"
  v-on:keydown="emitKeyDown"
  v-on:keypress="emitKeyPress"
  v-on:keyup="emitKeyUp"
  v-on:mouseenter="emitMouseEnter"
  v-on:mouseover="emitMouseOver"
  v-on:mousemove="emitMouseMove"
  v-on:mousedown="emitMouseDown"
  v-on:mouseup="emitMouseUp"
  v-on:click="emitClick"
  v-on:dblclick="emitDoubleClick"
  v-on:wheel="emitWheel"
  v-on:mouseleave="emitMouseLeave"
  v-on:mouseout="emitMouseOut"
  v-on:pointerlockchange="emitPointerLockChange"
  v-on:pointerlockerror="emitPointerLockError"
  v-on:blur="emitBlur"
  v-on:change="emitChange($event.target.value)"
  v-on:contextmenu="emitContextMenu"
  v-on:focus="emitFocus"
  v-on:input="emitInput($event.target.value)"
  v-on:invalid="emitInvalid"
  v-on:reset="emitReset"
  v-on:search="emitSearch"
  v-on:select="emitSelect"
  v-on:submit="emitSubmit"
>

Cependant, je suppose que ce serait toujours bien d'avoir un moyen d'exposer également les événements. Peut-être qu'une directive vide v-on pourrait transférer tous les écouteurs d'événement sur le parent à cet élément ?

<input
  v-bind="$attributes"
  v-on
>

Ou s'il y a plusieurs problèmes que nous voulons regrouper, nous pourrions revenir à quelque chose comme v-expose :

<input v-expose>

Cela s'est transformé en une discussion plus large sur la façon de simplifier la création de composants d'interface utilisateur, plutôt qu'une demande de fonctionnalité spécifique, je vais donc renommer ce problème. ??

Je suis en retard sur ce sujet, mais j'ai aussi quelques idées.

v-bind Solution actuelle et inconvénients

Tout d'abord, j'utilise déjà et j'adore la fonctionnalité v-bind="propObject" (si puissante). Par exemple, bootstrap-vue a un composant de lien interne qui est utilisé partout (boutons, navs, listes déroulantes, etc.). Le composant pivote pour devenir une ancre native contre un lien de routeur basé sur href contre to et la présence de vm.$router , il y a donc beaucoup de propriétés à transmettre conditionnellement à chacun de ces composants.

Notre solution était de mettre ces accessoires dans un mixin et d'utiliser v-bind="linkProps" avec un objet calculé. Cela fonctionne très bien, mais l'ajout de ce mixin à _tous les autres composants en utilisant le composant de lien_ demande encore beaucoup de temps.

v-expose Possibilités en utilisant v-bind

Personnellement, j'aime le concept de v-expose , et peut-être que cela pourrait fonctionner comme un emplacement par défaut + des emplacements nommés, puis utiliser des modificateurs pour accéder aux emplacements d'attribut nommés.

L'attribut par défaut _"slot"_ transmettrait toujours les attributs au composant lui-même (aucun changement), tandis que d'autres cibles nommées pourraient être spécifiées par le composant. Peut-être quelque chose comme ça :

<template>
  <my-component 
    <!-- Nothing new here -->
    v-bind="rootProps"
    <!-- This binds the `linkProps` object to the named attribute slot `link` -->
    v-bind.link="linkProps"
  />
</template>

À l'intérieur de MyComponent.vue :

<template>
  <div>
    <router-link v-expose="link" />
  </div>
</template>

Proxy d'événement

Je n'ai pas grand-chose à ajouter ici, sauf que .native est un modificateur incroyablement puissant. Résolu beaucoup de problèmes pour moi. Cela semble cependant largement inconnu des développeurs de Vue (je vois une bonne quantité de problèmes de bibliothèque d'interface utilisateur qui sont résolus en exposant les développeurs à cette fonctionnalité). J'ai placé un PR sur le site Web pour ajouter d'autres documents et un support de recherche sur le site et potentiellement optimisé pour la recherche Google.

Venant d'un argument sur la surface de l'API dans un autre numéro, je dois répéter que je ne suis pas un fan de l'idée v-expose . il introduit une autre « façon dont les choses fonctionnent », et ne fonctionne pas pour JSX sans également y implémenter quelque chose de spécial, etc.

Une chose que je respecte chez les gens de React est leur engagement envers une API mince et en utilisant autant que possible les fonctionnalités du langage. Dans cet esprit, réutiliser un modèle que nous avons déjà pour les accessoires pour les attributs semble bien mieux que d'introduire une autre abstraction.

<my-input
  type="file"
  mode="dropdown"
>
<template>
  <div>
    <input v-bind="$attributes">
    <dropdown v-bind="{ ...$props, $attributes.type }"/>
  </div>
</template

Ahh, je vois ce que tu dis maintenant. Et j'aime ça! Est-ce actuellement disponible ? vm.$attributes serait l'ajout à la place ?

Relisez vos commentaires. Je suis maintenant

Oui, $attributes serait l'ajout.

De plus, nous aurions besoin d'une option pour désactiver le comportement par défaut actuel d'application d'attributs à l'élément racine, comme ceci :
```html

Cela pourrait alors devenir un paramètre par défaut dans Vue 3.0 si nous décidons ensuite de le faire, ce qui entraîne un changement radical.

@LinusBorg Que $listeners , qui pourrait ressembler à ceci :

{
  input: function () { /* ... */ },
  focus: function () { /* ... */ },
  // ...
}

Alors peut-être que v-on pourrait accepter un objet, similaire à v-bind . On aurait donc :

<input v-bind="$attributes" v-on="$listeners">

Un problème que je prévois concerne les événements input / change , car v-model fonctionne légèrement différemment pour les composants que pour les éléments. Je ne sais pas non plus si nous voudrions à la fois $listeners et $nativeListeners . Je suppose que si $listeners étaient disponibles, alors le modificateur .native pourrait être obsolète.

De plus, en ce qui concerne l'option applyComponentAttrsToRoot , peut-être que exposeRootEl serait un bon nom, qui, lorsqu'il est défini sur false , pourrait désactiver à la fois les attributs appliqués automatiquement et l'événement .native expéditeur?

Il peut également être intéressant de pouvoir désactiver cela pour l'ensemble de l'application via Vue.config , ainsi que pour un seul composant.

J'ai récemment eu une idée similaire à propos de $listeners - elle est également disponible sur les composants fonctionnels via

context.data.listeners

Donc on se retrouverait avec $props , $attributes , $listeners ce qui me semble bien.

Il y a aussi #5578 qui demande la syntaxe d'objet v-on="{...}" comme je l'ai utilisé pour $attributes , cela s'intégrerait parfaitement.

Mais je ne suis pas sûr du modificateur .native. Pour que cela fonctionne à la fois avec les événements de composant et les écouteurs natifs, l'API finirait par être beaucoup plus compliquée, et l'utilisation est discutable, car un écouteur d'événement natif appliqué à l'élément racine attraperait toujours l'événement souhaité bouillonnant, donc cela pourrait ne pas être être nécessaire de l'affecter à un élément spécifique du modèle.

En général, je dirais que pour les bibliothèques de composants de bas niveau, les fonctions de rendu doivent être préférées lorsque les modèles deviennent difficiles à utiliser. Mais je suis d'accord que ce qui suit serait précieux:

  1. Désactivation du comportement par défaut de « l'application automatique des liaisons qui ne sont pas trouvées dans les props en tant qu'attributs de l'élément racine » (problème connexe : cela devrait-il également affecter les liaisons class et style ?)

  2. exposant un moyen plus simple d'"hériter" des liaisons externes du composant sur un élément interne qui n'est pas nécessairement la racine. Idéalement avec une cohérence entre les modèles et les fonctions de rendu.

ia kie comme vue, outils simples

Je veux juste dire que le PR pour cela dans la v2.4 est excellent ! ??

De la note de version

Les combiner nous permet de simplifier un composant comme celui-ci en ceci :

<div>
  <input v-bind="$attrs" v-on="$listeners">
</div>

Cela semble bien, mais ce n'est pas tout à fait vrai, car ce type de composants est conçu pour fonctionner avec le modèle v et, pour autant que je sache, le modèle v sur le composant d'emballage ne fonctionne pas correctement. Existe-t-il un exemple de transfert de v-model d'un composant d'emballage vers une entrée, par exemple ?
Le moyen le plus simple que j'ai trouvé:
https://jsfiddle.net/60xdxh0h/2/

Peut-être qu'avec un composant fonctionnel fonctionnant avec un modèle, ce serait plus simple

ce type de composants est conçu pour fonctionner avec v-model et pour autant que je sache, v-model sur le composant d'emballage ne fonctionne pas correctement.

Pourquoi penseriez-vous que? v-model n'est que du sucre de syntaxe pour un accessoire et un écouteur d'événement, les deux seront dans $attr/$props et peuvent donc être facilement transmis.

Je suppose que la seule chose qui nécessiterait une connaissance des options enfant serait si l'enfant modifiait les valeurs par défaut model , c'est vrai.

Mais il serait possible de les lire, selon les circonstances.

Bien sûr, c'est un sucre syntaxique, mais je veux dire que cela pourrait être déroutant à lire

Les combiner nous permet de simplifier un composant comme celui-ci en ceci :

lorsqu'il est basé sur l'exemple https://github.com/almino/semantic-ui-vue2/blob/master/src/elements/Input.vue , vous ne pouvez pas simplement transmettre les auditeurs directement pour obtenir le même contrôle. (ex: vous devez utiliser v-on:input="emitInput($event.target.value)" )

Quoi qu'il en soit, ce PR est précieux, bon travail!

@AlexandreBonaventure C'est parce que v-model fonctionne légèrement différemment sur les composants que sur les éléments. Les événements DOM fournissent un objet événement aux rappels, tandis que les événements de composant fournissent directement une valeur. Le résultat est que v-model _fonctionne_, mais la valeur liée est l'objet événement du DOM. ??

Je pense que vous avez raison de dire qu'il serait souhaitable que v-model travaille ici, mais je ne sais pas quel serait le meilleur endroit pour le résoudre. Quelques idées:

Peut-être qu'une propriété non énumérable pourrait être ajoutée à $listeners (par exemple __$listeners__: true , pour aider v-on détecter les utilisations de v-on="$listeners" . Ensuite, dans les cas où le $listeners est passé à v-on , chaque écouteur peut être encapsulé :

function (event) {
  listener(event.target.value)
}

Un inconvénient est maintenant que nous jetons des données. Si quelqu'un veut accéder à un keyCode , par exemple, il n'a pas de chance. Cependant, si les modificateurs étaient pris en charge pour la syntaxe des objets de v-on , nous pourrions résoudre ce problème en faisant en sorte que .native désactive le comportement de bouclage automatique.

@yyx990803 @LinusBorg Que

Oh je vois, vous faites référence à v-model sur rral. Les éléments de forme, j'y pensais sur les composants.

Vous ne pouvez/devriez pas utiliser cela sur les accessoires de toute façon, avec ou sans ce PR. Et dans les applications avancées, il est plutôt rare de l'utiliser (bien que réalisable).

@LinusBorg Je veux juste m'assurer que nous sommes sur la même CustomInput avec ce modèle :

<div>
  <input v-bind="$attrs" v-on="$listeners">
<div>

Vous ne vous attendriez pas à ce que le code ci-dessous fonctionne ?

<CustomInput v-model="myValue" />

Je m'attendrais à ce que cela fonctionne - mais de la façon dont j'ai compris alexandre, il faisait référence au modèle v sur l'élément, pas au composant - qui ne fonctionne finalement qu'avec l'état local en mutation.

J'essayais de dire ce que @chrisvfritz a expliqué dans son dernier post. (L'anglais n'est pas ma langue maternelle désolé :))

@LinusBorg le problème avec le fait de faire cela dans la dernière version est qu'il est toujours considéré comme un anti-modèle et déclenche un avertissement concernant la mutation de l'état.

Il est extrêmement utile de faire fonctionner ce qui précède lorsque la propriété value est autre chose qu'une chaîne. Prenons par exemple un composant combo où j'essaie d'utiliser des énumérations importées de ma propre bibliothèque comme valeurs pour les options sélectionnées :

<template>
    <select class="combo" v-model="value" v-on="$listeners"> 
      <option v-for="(item, key) in items" :value="item">{{key}}</option>
    </select>
</template>

<script>
export default {
    props: {
        items: {
            type: Object,
            required: true
        },

        value: {}
    }
}
</script>

Voici un exemple de l'une des listes que j'utilise pour les éléments du parent :

            execList: {
                "None": ACT_EXEC_TYPES.NONE,
                "Function": ACT_EXEC_TYPES.FUNCTION,
                "Code": ACT_EXEC_TYPES.CODE
            }

Et comment j'utilise le composant combo :

<combo :items="execList" v-model="selectedAction.execType"/>

J'essaie de faire en sorte que cela fonctionne depuis 2 jours maintenant et je suis toujours très frustré. Le problème est que $event.target.value est toujours une chaîne et n'est pas évalué comme il devrait l'être dans :value .

@LinusBorg @AlexandreBonaventure @RobertBColton Je viens d' ouvrir un numéro sur lequel nous pouvons concentrer les discussions futures sur ce problème.

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