Vue: [Abandonné] RFC : Simplifier l'utilisation des emplacements limités

Créé le 11 déc. 2018  ·  36Commentaires  ·  Source: vuejs/vue

Ceci est un suivi de https://github.com/vuejs/vue/issues/7740#issuecomment -371309357

Rationnel

Problèmes avec l'utilisation actuelle de l'emplacement :

  • Verbeux si vous utilisez <template slot-scope>
  • Limité à un élément/composant utilise slot-scope directement sur l'élément slot.

Proposition

Introduisez une nouvelle directive v-scope , qui ne peut être utilisée que sur les composants :

<comp v-scope="scope">
  {{ scope.msg }}
</comp>

Cela fonctionnerait de la même manière que slot-scope pour l'emplacement de portée par défaut pour <comp> (avec <comp> fournissant la valeur de portée). Cela fonctionne donc aussi avec la déconstruction :

<comp v-scope="{ msg }">
  {{ msg }}
</comp>

Pourquoi une nouvelle directive

Je pense que l'équipe a brièvement discuté de ce que j'ai proposé dans https://github.com/vuejs/vue/issues/7740#issuecomment -371309357 sur Slack il y a quelque temps, mais je ne pouvais plus trouver l'enregistrement du chat. Voici le raisonnement qui sous-tend une nouvelle directive :

  • slot-scope été introduit comme un attribut spécial au lieu d'une directive (attributs commençant par v- préfixe) car slot est un attribut, et nous voulions garder les attributs liés aux slots cohérents . slot a à son tour été introduit comme un attribut non-directif car nous voulons que l'utilisation reflète l'utilisation réelle des slots dans la norme Shadow DOM. Nous avons pensé qu'il serait préférable d'éviter d'avoir notre propre v-slot parallèle quand il y a quelque chose qui est conceptuellement le même dans la norme.

  • À l'origine, slot-scope été conçu pour n'être utilisable que sur des éléments <template> qui agissent comme des conteneurs abstraits. Mais c'était verbeux - nous avons donc introduit la possibilité de l'utiliser directement sur un élément slot sans le wrapping <template> . Cependant, cela rend également impossible l'utilisation de slot-scope directement sur le composant lui-même, car cela entraînerait une ambiguïté comme illustré ici .

  • J'ai pensé à ajouter des modificateurs ou des préfixes spéciaux à slot-scope afin que nous puissions l'utiliser directement sur un composant pour indiquer que le contenu de son emplacement doit être traité comme l'emplacement par défaut, mais ni un modificateur ni un préfixe comme $ semblent être la bonne solution. Le modificateur par conception ne doit être appliqué qu'aux directives, tandis que la nouvelle syntaxe spéciale pour un seul cas d'utilisation est incompatible avec l'ensemble de la conception de la syntaxe.

  • Pendant très longtemps, nous avons évité d'ajouter plus de directives, dont une partie étant la syntaxe du modèle est quelque chose que nous voulons garder aussi stable que possible, une partie étant que nous voulons garder les directives de base au minimum et ne faire que des choses que les utilisateurs ne peuvent pas facilement faire dans l'espace utilisateur. Cependant, dans ce cas, l'utilisation des créneaux horaires est suffisamment importante, et je pense qu'une nouvelle directive peut être justifiée pour rendre son utilisation beaucoup moins bruyante.

Préoccupations

  • L'expression acceptée par v-scope est différente de la plupart des autres directives : elle attend un nom de variable temporaire (qui peut aussi être une déconstruction), mais non sans priorité : elle agit comme la partie alias de v-for . Donc, conceptuellement, v-scope tombe dans le même camp avec v-for tant que directive structurelle qui crée des variables temporaires pour sa portée interne.

  • Cela casserait le code des utilisateurs si l'utilisateur a une directive personnalisée nommée v-scope et est utilisée sur un composant.

    • Étant donné que les directives personnalisées de la v2 sont principalement axées sur les manipulations directes du DOM, il est relativement rare de voir des directives personnalisées utilisées sur des composants, encore plus pour quelque chose qui s'appelle v-scope , donc l'impact devrait être minime.

    • Même dans le cas où cela se produirait réellement, il est simple à gérer en renommant simplement la directive personnalisée.

discussion intend to implement

Commentaire le plus utile

Décomposer le problème

En essayant de synthétiser, il semble que nous voulions une solution qui :

  • réduire le passe-partout nécessaire pour accéder aux données à partir des emplacements délimités
  • éviter d'impliquer que les données du slot par défaut sont disponibles dans les slots nommés
  • minimiser le nombre de nouvelles API que nous devons introduire
  • ne réinventez pas les API où la spécification des composants Web a déjà une solution acceptable
  • rester explicite, pour éviter toute confusion sur la provenance des données

J'ai peut-être une solution qui résout tout cela ! 🤞 Avec $event pour v-on , nous avons un précédent de fournir des expressions exécutées dans une fonction implicite avec un argument nommé. Les gens semblent apprécier cette commodité et je ne vois pas beaucoup de confusion causée par cela, alors peut-être devrions-nous suivre cet exemple pour les emplacements limités.

Solution proposée

Au lieu d'utiliser v-scope , nous pourrions rendre la portée du slot disponible en tant que $slot . Par exemple:

<TodoList :todos="todos">
  {{ $slot.todo.text }}

  <template slot="metaBar">
    You have {{ $slot.totals.incomplete }} todos left.
  </template>
</TodoList>

Pour le contexte, le modèle enfant pourrait ressembler à :

<ul>
  <li v-for="todo in todos">
    <slot :todo="todo" />
  </li>
</ul>

<div>
  <slot name="metaBar" v-bind="itemsStats" />
</div>

Lorsque les slots sont imbriqués, l'objet $slot fusionne les données des slots, les slots les plus à l'intérieur étant prioritaires. Par exemple, dans :

<Outer>
  <Middle>
    <Inner>
      {{ $slot }}
    </Inner>
  </Middle>
</Outer>

la fusion pourrait ressembler à ceci :

$slot = {
  ...outerSlotData,
  ...middleSlotData,
  ...innerSlotData
}

Vous vous demandez peut-être comment gérer le chevauchement des espaces de noms et dans 99,9% des cas, je ne pense vraiment pas que ce sera un problème. Par exemple:

<UserData id="chrisvfritz">
  My email is {{ $slot.user.email }}, but you can reach Sarah at
  <UserData id="sdras">
    {{ $slot.user.email }}
  </UserData>.
</UserData>

Dans l'exemple ci-dessus, $slot.user aura toujours la valeur correcte, malgré les portées d'imbrication. Cependant, vous aurez parfois vraiment besoin d'accéder aux deux propriétés en même temps, comme dans :

<UserData id="chrisvfritz">
  <template slot-scope="me">
    <UserData id="sdras">
      <template slot-scope="sarah">
        <CollaborationPageLink :user-ids="[me.user.id, sarah.user.id]">
          View your collaboration history
        </CollaborationPageLink>
      </template>
    </UserData>
  </template>
</UserData>

Pour ces rares cas extrêmes, les utilisateurs peuvent toujours utiliser slot-scope avec son comportement actuel en tant que trappe d'évacuation . Mais v-scope serait toujours inutile, car si je comprends bien, son but serait de simplifier les cas d'utilisation les plus courants, ce que l'objet $slot aurait déjà accompli sans poser les mêmes problèmes.

Avantages

  • Lorsqu'un utilisateur souhaite accéder aux données fournies par un slot, le seul passe-partout est $slot , ce qui est à peu près aussi bref que possible tout en restant explicite. Il est immédiatement évident que les données proviennent d'un emplacement, sans avoir besoin de rechercher dans le composant pour voir où une propriété est définie.

  • Pour savoir à quelles données ils peuvent accéder, les utilisateurs n'ont qu'à penser à quel(s) slot(s) le contenu sera rendu . Je pense qu'avec v-scope , il y aurait beaucoup de confusion avec les personnes supposant que cela fonctionne comme v-for , car c'est la seule autre directive qui définit les propriétés de contexte étendues.

  • Les utilisateurs n'ont pas besoin d'être familiers et à l'aise avec la déstructuration pour utiliser des emplacements limités , réduisant ainsi la courbe d'apprentissage.

  • Dans certains cas, en particulier avec de nombreux emplacements imbriqués qui fournissent tous un état, il ne sera pas évident de savoir de quel composant un état provient et les utilisateurs devront à nouveau atteindre slot-scope . Cela peut sembler être un inconvénient, mais lorsque je vois des emplacements à portée imbriquée, c'est presque toujours pour le modèle de fournisseur d'état, que je pense fortement être un anti-modèle. Voici mon raisonnement . Ainsi, le fait que de nombreux emplacements à portée imbriquée nécessiteraient plus de passe-partout pourrait en fait réduire l'utilisation d'anti-modèles .

  • La surface de l'API est considérablement réduite , car la plupart des développeurs Vue n'auront qu'à se souvenir de $slot , qui n'est qu'un objet.

  • En utilisant une propriété préfixée $ , nous construisons sur l'API de slot de composant Web d'une manière qui indique clairement que $slot est une chose Vue.

  • Actuellement, les bibliothèques font souvent quelque chose comme <slot v-bind="user" /> pour que leurs utilisateurs puissent enregistrer quelques caractères avec slot-scope="user" au lieu de slot-scope="{ user } . Cela semble élégant à première vue, mais j'en suis venu à ressentir comme un anti-modèle. Le problème se pose lorsque le composant veut exposer des données _autres que_ l'utilisateur. Ensuite, ils ont deux choix : apporter une modification radicale à leur API ou forcer cette nouvelle propriété sur l'objet user , même si cela n'a pas grand-chose à voir avec l'utilisateur. Ni l'un ni l'autre n'est une excellente option. Heureusement, $slot éliminerait la tentation de rendre les composants moins évolutifs , car même si $slot est toujours plus court que $slot.user , vous perdez un contexte important en aliasant l'utilisateur en tant que $slot .

Désavantages

  • Dans certains emplacements à portée imbriquée, il existe des cas extrêmes où il ne sera pas évident de _de quel_ composant proviennent certaines données. Dans ces cas, cependant, les utilisateurs peuvent toujours atteindre slot-scope lorsqu'ils ont besoin d'un maximum d'explicitation, donc je ne pense pas que ce soit un gros problème. De plus, comme je l'ai mentionné plus tôt, je pense qu'il est très probable que l'utilisateur se tire une balle dans le pied avec les composants du fournisseur d'État s'il rencontre ce problème en premier lieu.

Tous les 36 commentaires

Cela semble bon. Je pensais utiliser l'argument pour fournir un nom d'emplacement, mais cela permettrait d'utiliser plusieurs directives v scope sur le même composant et donc de réutiliser le contenu fourni au composant. Ce dont je ne sais pas si c'est utile ou si cela pourrait entraîner des problèmes

Globalement, cela améliorera l'utilisation de l'API des composants sans rendu, qui sont de plus en plus utilisés avec le temps 🙌

Si je comprends bien, v-scope ne sert qu'un seul cas d'utilisation. Au lieu d'écrire :

<foo>
  <template slot-scope="{ item }">{{ item.id }}</template>
</foo>

Nous pouvons écrire:

<foo v-scope="{ item }">{{ item.id }}</foo>

Cela réduit beaucoup le bruit dans ce cas, mais cela semble être le seul cas. Cela ressemble à une sorte d'exagération d'introduire une nouvelle directive (qui fonctionne assez différemment des autres directives, même à partir de v-for car elle ne fonctionne que sur les composants et repose sur la logique <slot> dessous). Une autre préoccupation est que lorsque je recherche v-scope dans ce référentiel , les deux seules occurrences discutent toutes deux de la réduction de la portée des données d'un sous-arbre de modèle (https://github.com/vuejs/vue/issues/5269 #issuecomment-288912328, https://github.com/vuejs/vue/issues/6913), comme le fonctionnement de with en JavaScript.

Je l'aime bien. Une question restante pour moi serait de savoir comment nous voulons traiter les créneaux nommés. Si nous voulons permettre aux slots nommés d'utiliser la même directive, nous devons également l'autoriser sur <template> :

<comp v-scope="scope">
  {{ scope.msg }} <!-- default slot, inheriting from the scope from above -->
  <template slot="someName" v-scope="otherScope">
    <p>{{ otherScope }}</p>
    <div> whatever</div>
  </template>
</comp>

Nous pourrions utiliser cette opportunité pour déprécier sot-scope et le supprimer dans Vue 3, en le remplaçant par v-scope

Je pense que nous ne devrions certainement pas nous retrouver avec deux concepts différents ( directive v-scope et slot-scope attribut) pour la même chose.

Note : Maintenant, en regardant cela, les gens pourraient avoir l'impression de la hiérarchie des éléments et
directives, qu'ils peuvent accéder à otherScope et scope dans le slot nommé. Peut-être un inconvénient.

@LinusBorg pour le nom, un argument pourrait faire v-scope:slotName . Je pense que le but de n'autoriser cela que sur les composants est de remplacer ce que @Justineo a dit (https://github.com/vuejs/vue/issues/9180#issuecomment-446168296)

Note : Maintenant, en regardant cela, les gens pourraient avoir l'impression de la hiérarchie des éléments et
directives, qu'ils peuvent accéder à otherScope et scope dans le slot nommé. Peut-être un inconvénient.

Ça marche, non ? 🤔 Je suis presque sûr d'avoir imbriqué des portées de fente

Ça marche, non ? 🤔 Je suis presque sûr d'avoir imbriqué des portées de fente

Je ne les ai pas imbriqués, cependant, ce sont des machines à sous pour frères et sœurs. J'ai défini la portée du slot par défaut avec v-scope sur le composant, et le slot nommé (qui est un slot frère ) avec un <template slot="someName">
Voir? C'est déroutant ^^

@LinusBorg Je pense que l'utilisation serait déroutante. Je pense que conceptuellement c'est :

  • Lorsque v-scope est utilisé directement sur le composant, cela signifie que vous utilisez uniquement l'emplacement par défaut.
  • Si vous souhaitez utiliser des emplacements nommés... vous devez toujours utiliser slot + slot-scope .

Je suis d'accord qu'avoir à la fois v-scope et slot-scope pourrait être incohérent/déroutant, en particulier pour les nouveaux utilisateurs qui ne savent pas comment nous sommes arrivés à la conception actuelle.

Décomposer le problème

En essayant de synthétiser, il semble que nous voulions une solution qui :

  • réduire le passe-partout nécessaire pour accéder aux données à partir des emplacements délimités
  • éviter d'impliquer que les données du slot par défaut sont disponibles dans les slots nommés
  • minimiser le nombre de nouvelles API que nous devons introduire
  • ne réinventez pas les API où la spécification des composants Web a déjà une solution acceptable
  • rester explicite, pour éviter toute confusion sur la provenance des données

J'ai peut-être une solution qui résout tout cela ! 🤞 Avec $event pour v-on , nous avons un précédent de fournir des expressions exécutées dans une fonction implicite avec un argument nommé. Les gens semblent apprécier cette commodité et je ne vois pas beaucoup de confusion causée par cela, alors peut-être devrions-nous suivre cet exemple pour les emplacements limités.

Solution proposée

Au lieu d'utiliser v-scope , nous pourrions rendre la portée du slot disponible en tant que $slot . Par exemple:

<TodoList :todos="todos">
  {{ $slot.todo.text }}

  <template slot="metaBar">
    You have {{ $slot.totals.incomplete }} todos left.
  </template>
</TodoList>

Pour le contexte, le modèle enfant pourrait ressembler à :

<ul>
  <li v-for="todo in todos">
    <slot :todo="todo" />
  </li>
</ul>

<div>
  <slot name="metaBar" v-bind="itemsStats" />
</div>

Lorsque les slots sont imbriqués, l'objet $slot fusionne les données des slots, les slots les plus à l'intérieur étant prioritaires. Par exemple, dans :

<Outer>
  <Middle>
    <Inner>
      {{ $slot }}
    </Inner>
  </Middle>
</Outer>

la fusion pourrait ressembler à ceci :

$slot = {
  ...outerSlotData,
  ...middleSlotData,
  ...innerSlotData
}

Vous vous demandez peut-être comment gérer le chevauchement des espaces de noms et dans 99,9% des cas, je ne pense vraiment pas que ce sera un problème. Par exemple:

<UserData id="chrisvfritz">
  My email is {{ $slot.user.email }}, but you can reach Sarah at
  <UserData id="sdras">
    {{ $slot.user.email }}
  </UserData>.
</UserData>

Dans l'exemple ci-dessus, $slot.user aura toujours la valeur correcte, malgré les portées d'imbrication. Cependant, vous aurez parfois vraiment besoin d'accéder aux deux propriétés en même temps, comme dans :

<UserData id="chrisvfritz">
  <template slot-scope="me">
    <UserData id="sdras">
      <template slot-scope="sarah">
        <CollaborationPageLink :user-ids="[me.user.id, sarah.user.id]">
          View your collaboration history
        </CollaborationPageLink>
      </template>
    </UserData>
  </template>
</UserData>

Pour ces rares cas extrêmes, les utilisateurs peuvent toujours utiliser slot-scope avec son comportement actuel en tant que trappe d'évacuation . Mais v-scope serait toujours inutile, car si je comprends bien, son but serait de simplifier les cas d'utilisation les plus courants, ce que l'objet $slot aurait déjà accompli sans poser les mêmes problèmes.

Avantages

  • Lorsqu'un utilisateur souhaite accéder aux données fournies par un slot, le seul passe-partout est $slot , ce qui est à peu près aussi bref que possible tout en restant explicite. Il est immédiatement évident que les données proviennent d'un emplacement, sans avoir besoin de rechercher dans le composant pour voir où une propriété est définie.

  • Pour savoir à quelles données ils peuvent accéder, les utilisateurs n'ont qu'à penser à quel(s) slot(s) le contenu sera rendu . Je pense qu'avec v-scope , il y aurait beaucoup de confusion avec les personnes supposant que cela fonctionne comme v-for , car c'est la seule autre directive qui définit les propriétés de contexte étendues.

  • Les utilisateurs n'ont pas besoin d'être familiers et à l'aise avec la déstructuration pour utiliser des emplacements limités , réduisant ainsi la courbe d'apprentissage.

  • Dans certains cas, en particulier avec de nombreux emplacements imbriqués qui fournissent tous un état, il ne sera pas évident de savoir de quel composant un état provient et les utilisateurs devront à nouveau atteindre slot-scope . Cela peut sembler être un inconvénient, mais lorsque je vois des emplacements à portée imbriquée, c'est presque toujours pour le modèle de fournisseur d'état, que je pense fortement être un anti-modèle. Voici mon raisonnement . Ainsi, le fait que de nombreux emplacements à portée imbriquée nécessiteraient plus de passe-partout pourrait en fait réduire l'utilisation d'anti-modèles .

  • La surface de l'API est considérablement réduite , car la plupart des développeurs Vue n'auront qu'à se souvenir de $slot , qui n'est qu'un objet.

  • En utilisant une propriété préfixée $ , nous construisons sur l'API de slot de composant Web d'une manière qui indique clairement que $slot est une chose Vue.

  • Actuellement, les bibliothèques font souvent quelque chose comme <slot v-bind="user" /> pour que leurs utilisateurs puissent enregistrer quelques caractères avec slot-scope="user" au lieu de slot-scope="{ user } . Cela semble élégant à première vue, mais j'en suis venu à ressentir comme un anti-modèle. Le problème se pose lorsque le composant veut exposer des données _autres que_ l'utilisateur. Ensuite, ils ont deux choix : apporter une modification radicale à leur API ou forcer cette nouvelle propriété sur l'objet user , même si cela n'a pas grand-chose à voir avec l'utilisateur. Ni l'un ni l'autre n'est une excellente option. Heureusement, $slot éliminerait la tentation de rendre les composants moins évolutifs , car même si $slot est toujours plus court que $slot.user , vous perdez un contexte important en aliasant l'utilisateur en tant que $slot .

Désavantages

  • Dans certains emplacements à portée imbriquée, il existe des cas extrêmes où il ne sera pas évident de _de quel_ composant proviennent certaines données. Dans ces cas, cependant, les utilisateurs peuvent toujours atteindre slot-scope lorsqu'ils ont besoin d'un maximum d'explicitation, donc je ne pense pas que ce soit un gros problème. De plus, comme je l'ai mentionné plus tôt, je pense qu'il est très probable que l'utilisateur se tire une balle dans le pied avec les composants du fournisseur d'État s'il rencontre ce problème en premier lieu.

Je pense qu'une propriété $ -préfixée devrait être cohérente dans une instance entière de Vue. Je ne sais pas si cela créera une confusion en injectant implicitement un $slot dans chaque emplacement et ils représentent en fait un argument local à l'intérieur de ces portées d'emplacement. C'est une sorte de convention (selon mon observation) que les propriétés $ -préfixées représentent quelque chose de disponible pour chaque instance de Vue, elles peuvent donc être utilisées n'importe où, y compris les hooks de cycle de vie, les méthodes et les fonctions de rendu. L'ajout de ce $slot spécial brisera cette convention.

@chrisvfritz c'est une proposition intéressante, mais elle repose en quelque sorte sur l'unification des emplacements normaux et des emplacements limités (donc peut-être quelque chose qui peut être envisagé dans la v3). Le plus gros problème est que le contenu de l'emplacement doit-il être disponible dans le $slots ou le $scopedSlots du composant enfant ? Le seul indice est la présence de $slot quelque part dans les expressions (peut être n'importe où dans l'arborescence), ce qui n'est pas aussi explicite que slot-scope (qui ne peut être qu'à la racine de l'emplacement). Bien que techniquement nous puissions le détecter dans le compilateur, pour l'utilisateur, le slot passera de $slots à $scopedSlots chaque fois que l'utilisateur commencera à utiliser $slot dans le modèle...

Cela aura-t-il de gros impacts sur les utilisateurs de JSX ?

@donnysim non, cela n'affecte en aucun cas JSX.

Je pense qu'une propriété $ -préfixée devrait être cohérente dans une instance entière de Vue. Je ne sais pas si cela créera une confusion en injectant implicitement un $slot dans chaque emplacement et ils représentent en fait un argument local à l'intérieur de ces portées d'emplacement.

@Justineo J'ai eu la même pensée au début, mais nous le faisons pour $event et personne ne semble se plaindre, donc il existe déjà un précédent et nous avons la preuve que les utilisateurs ne sont généralement pas confus et apprécient vraiment la commodité.

Le plus gros problème est que le contenu de l'emplacement doit-il être disponible dans le $slots ou le $scopedSlots du composant enfant ?

@yyx990803 Excellente question ! J'ai une idée qui pourrait fonctionner. Et si dans les cas où c'est ambigu (emplacements imbriqués utilisant $slot ), nous compilions simplement tous les emplacements dans un emplacement défini, mais rendions également tous les $scopedSlots disponibles sous $slots tant que getters ? Par exemple, quelque chose comme :

for (const slotName in vm.$scopedSlots) {
  // Don't override existing slots of the same name,
  // since that's _technically_ possible right now.
  if (vm.$slots[slotName]) continue

  Object.defineProperty(vm.$slots, slotName, {
    get: vm.$scopedSlots[slotName],
    enumerable: true,
    configurable: true,
    writable: true
  })
}

Ainsi, dans le cas de :

<A>
  <B>
    {{ $slot.foo }}
  </B>
</A>

Nous pourrions compiler quelque chose comme :

// Assume new render helper:
// _r = mergeSlotData

_c('A', {
  scopedSlots: _u([
    {
      key: 'default',
      fn: function(_sd) {
        var $slot = _r($slot, _sd)
        return [
          _c('B', {
            scopedSlots: _u([
              {
                key: 'default',
                fn: function(_sd) {
                  var $slot = _r($slot, _sd)
                  return [_v(_s($slot.foo))]
                },
              },
            ]),
          }),
        ]
      },
    },
  ]),
}) 

Et peu importe que foo provienne de <A> ou de <B> , car à l'intérieur de ces composants à la fois this.$slots.default et this.$scopedSlots.default(someData) fonctionneront.

Quelques mises en garde :

  • Dans les cas d'emplacements imbriqués et que seuls certains d'entre eux sont réellement étendus, les fonctions de rendu compilées à partir de modèles utilisant $slot seraient légèrement plus grandes que si elles utilisaient slot-scope . Pas très significativement, donc je pense que ça va.

  • Je ne peux pas penser à un exemple réel, mais il peut y avoir des cas extrêmes où un utilisateur itère plus de $slots / $scopedSlots et que des propriétés nouvelles ou en double peuvent entraîner un comportement inattendu. J'ai itéré sur les noms dynamiquement $slots et $scopedSlots auparavant pour activer certains modèles intéressants, mais ce changement n'affecterait aucun des cas d'utilisation que j'ai rencontrés.

Les pensées?

nous le faisons pour $event et personne ne semble se plaindre

@chrisvfritz Oh, j'ai raté le truc de $event . Bien que ce soit clairement une exception à la convention (j'ai l'habitude de croire que c'est 😅), $event n'est disponible que dans une portée très limitée (uniquement à l'intérieur des littéraux d'attribut v-bind et pas d'imbrication).

Et pour le proxying $scopedSlots sur $slots :

J'avais l'habitude de croire que les emplacements et les emplacements à portée sont des concepts différents et que Vue a une utilisation distincte pour eux, ce qui signifie que je peux avoir à la fois un emplacement et un emplacement à portée partageant le même nom mais servant à des fins différentes. Mais plus tard, j'ai découvert que Vue se repliait sur l'emplacement avec le même nom lorsque l'emplacement spécifié n'était pas disponible. Pour moi, cela signifie que nous devrions les considérer comme la même chose, mais puisque nous avons à la fois $slots et $scopedSlots disponibles sur chaque instance, nous pouvons toujours les utiliser dans les fonctions de rendu d'une manière plus flexible/inattendue , comme utiliser un emplacement par défaut pour remplacer tous les éléments de la liste et un emplacement défini par défaut pour remplacer un seul élément. Ce n'est en fait pas encouragé (nous n'avons pas suggéré l'utilisation recommandée à ce sujet dans nos documents et le guide de style AFAIK) car nous sommes susceptibles de les fusionner en un seul concept de slot dans 3.0, mais le faire dans 2.x cassera certains usages autorisés depuis assez longtemps.

nous allons probablement les fusionner en un seul concept de slot dans la 3.0, mais le faire dans la 2.x annulera certaines utilisations autorisées depuis assez longtemps.

@Justineo Je me $slots et $scopedSlots utilisent le même espace de noms, serait toujours possible car le comportement de slot-scope resterait inchangé. Par exemple:

<div>
  Default slot
  <template slot-scope="foo">
    Default scoped slot {{ foo }}
  </template>
</div>

fonctionnerait toujours exactement de la même manière. Cela a-t-il du sens?

@chrisvfritz

Je ne parlais pas de les fusionner dans 2.x. Je disais si tu fais ça :

rendre tous les $scopedSlots disponibles sous $slots tant que getters

Vous ne pouvez plus distinguer $slots.foo de $scopedSlots.foo et les utiliser séparément dans les fonctions de rendu.

Vous ne pouvez plus distinguer $slots.foo de $scopedSlots.foo et les utiliser séparément dans les fonctions de rendu.

Honnêtement, je serais heureux si tout (slots et scopedSlots) était accessible dans scopedSlots en tant que fonctions, cela signifie simplement qu'il n'y a plus de vérifications inutiles du slot à rendre. Du point de vue des composants, le type de slot utilisé par le développeur ne fait vraiment aucune différence. Provenant d'un utilisateur mixte de JSX et de syntaxe de modèle.

@donnysim Oui, je suis d'accord avec vous pour qu'ils soient fusionnés. J'ai expliqué pourquoi j'ai une telle préoccupation dans https://github.com/vuejs/vue/issues/9180#issuecomment -447185512. Il s'agit de compatibilité descendante.

Vous ne pouvez plus distinguer $slots.foo de $scopedSlots.foo et les utiliser séparément dans les fonctions de rendu.

@Justineo Vous _pouvez_ en fait, tant que nous traitons d'abord $slots et ne remplaçons pas les emplacements déjà définis. Voir l'exemple d'implémentation que j'ai posté ci-dessus :

for (const slotName in vm.$scopedSlots) {
  // Don't override existing slots of the same name,
  // since that's _technically_ possible right now.
  if (vm.$slots[slotName]) continue

  Object.defineProperty(vm.$slots, slotName, {
    get: vm.$scopedSlots[slotName],
    enumerable: true,
    configurable: true,
    writable: true
  })
}

@chrisvfritz

Considérez l'exemple suivant :

render (h) {
  if (!this.$slots.default) {
    return h(
      'div',
      this.items.map(item => this.$scopedSlots.default(item))
    )
  }
  return h('div', this.$slots.default)
}

Si l'utilisateur du composant n'a transmis que vm.$scopedSlots.default , il devrait parcourir this.items et restituer item s dans l'emplacement de portée. Mais maintenant que vm.$scopedSlot.default est maintenant l'emplacement à portée et qu'il existe, l'emplacement à portée sera invoqué à la place de l'emplacement.

@Justineo Je pense que ce cas d'utilisation serait très rare, mais le modèle serait toujours possible en changeant :

if (!this.$slots.default) {

à:

if (this.$scopedSlots.default) {

ou, si l'utilisateur fournit parfois un slot limité qui est censé être ignoré pour une raison quelconque :

if (!Object.getOwnPropertyDescriptor(this.$slots, 'default').value) {

Je ne considérerais toujours pas cela comme un changement décisif. Premièrement, nous n'avons jamais documenté/recommandé l'utilisation de slots et de slots scoped du même nom, donc cela n'a jamais fait partie du contrat public. Deuxièmement, comme vous l'avez mentionné, les slots délimités dans les modèles reviennent déjà à un slot non délimité du même nom, ce qui constitue une bonne preuve historique que nous n'avons jamais voulu que la limite délimitée/non délimitée soit utilisée comme ceci.

Cela a-t-il du sens? Aussi, avez-vous vu de vrais cas d'utilisation pour réutiliser les noms de slots ? Ou pouvez-vous en penser ? Je ne peux pas, mais s'il existe des modèles utiles et uniques, il serait bon de les connaître maintenant car cela influencerait également notre décision de les fusionner dans Vue 3.

Je pense que les slots étendus et les slots avec le même nom à des fins différentes sont
Une mauvaise idée. Je l'ai utilisé dans la version pré de Vue promise et je l'ai supprimé
parce que c'était déroutant. Et maintenant, je dois vérifier à la fois l'emplacement par défaut
et un slot limité afin que le développeur puisse fournir un slot sans consommer les données.
Et si je me souviens bien, vous ne pouvez pas avoir le même nom lorsque vous utilisez
modèles.

@chrisvfritz

Je ne disais pas que le modèle est utile et devrait être pris en charge. Cela peut en effet être source de confusion. Je disais juste qu'il est possible de casser le code existant. Nous n'avons jamais documenté le partage du même nom entre les emplacements et les emplacements limités, mais nous n'avons jamais suggéré aux utilisateurs de ne pas le faire non plus. Et la logique de secours dans les modèles n'est pas documentée. Ce n'est peut-être pas intentionnel, mais au moins je croyais moi-même que c'était un usage légitime jusqu'à ce que j'apprenne la logique de secours dans les modèles...

@posva Merci d'avoir partagé votre expérience !

@Justineo À votre avis, pensez-vous que ce cas limite causerait trop de douleur aux utilisateurs, ou est-il acceptable pour la commodité de l'amélioration de l'API de slot dans Vue 2.x?

Cela a été déplacé vers "Todo" - Avons-nous un consensus sur ce qu'il faut mettre en œuvre ?

J'y ai pensé la semaine dernière. J'aime la proposition de variable $slot @chrisvfritz et j'essayais de trouver les bloqueurs d'implémentation. Voici ce que je pense faisable :

  • Tous les emplacements sont compilés en tant que fonctions (comme dans 3.0), donc plus d'emplacements statiques (cela conduit à un suivi plus précis de la mise à jour des composants)
  • $scopedSlots exposera chaque emplacement en tant que fonction
  • $slots exposera chaque emplacement suite à l'appel de la fonction correspondante avec un objet vide.

Donc ce qui suit serait équivalent :

this.$slots.default
// would be the same as
this.$scopedSlots.default({} /* $slot */)

Avec les modifications internes ci-dessus, $slots et $scopedSlots sont essentiellement unifiés mais devraient être rétrocompatibles ! Cela devrait également faciliter la migration vers la 3.0.

Quelques exemples d'utilisation utilisant la variable $slot proposée :

<!-- list and item composition -->
<fancy-list>
  <fancy-item :item="$slot.item"/>
</fancy-list>
<!-- hypothetical data fetching component -->
<fetch :url="`/api/posts/${id}`">
  <div slot="success">{{ $slot.data }}</div>
  <div slot="error">{{ $slot.error }}</div>
  <div slot="pending">pending...</div>
</fetch>

Je ne pense pas que la fusion des étendues imbriquées soit une bonne idée. Je pense qu'il est préférable d'être explicite lorsque l'imbrication est impliquée (c'est-à-dire utilisez toujours slot-scope si vous avez des emplacements de portée imbriqués):

<!-- nested usage -->
<foo>
  <bar slot-scope="foo">
    <baz slot-scope="bar">
      {{ foo }} {{ bar }} {{ $slot }}
    </baz>
  </bar>
</foo>

Dans votre dernier exemple, à quel emplacement $slot référence ?

@Akryum c'est { ...foo, ...bar } .

@Akryum @Justineo hmm Je peux dire que cela pourrait être déroutant... nous avons rendu slot-scope utilisable sur l' élément racine d'un slot , c'est-à-dire ici foo est la portée du slot fournie par <foo/> , bar est fourni par <bar/> et $slot est fourni par <baz/> ... maintenant je pense que c'était une erreur d'autoriser une telle utilisation, car l'utilisation la plus intuitive dans ce cas serait quelque chose comme ceci :

<!-- nested usage -->
<foo slot-scope="foo">
  <bar slot-scope="bar">
    <baz slot-scope="baz">
      {{ foo }} {{ bar }} {{ baz }}
    </baz>
  </bar>
</foo>

cela peut prêter à confusion

une utilisation plus intuitive dans ce cas serait quelque chose comme ça

Tout à fait d'accord. Aussi, nous pouvons continuer à utiliser la déstructuration.
Par exemple:
```vue

<FieldValidation field="login" slot-scope="{ value, setValue, error }">
  <LoginInput
    :value="value"
    :error="error"
    @input="setValue"
  />
</FieldValidation>

<FieldValidation field="password" slot-scope="{ value, setValue, error }">
  <PasswordInput
    :value="value"
    :error="error"
    @input="setValue"
  />
</FieldValidation>

Fermé via 7988a554 et 5d52262f

Sommaire:

  • Prend en charge la variable $slot dans tous les emplacements. (La présence de $slot provoque la compilation du slot en tant que slot scoped).

  • Tous les slots, y compris les slots normaux, sont maintenant exposés sur this.$scopedSlots tant que fonctions. Cela signifie que si this.$slots.default existe, this.$scopedSlots.default() renverra sa valeur. Cela permet aux utilisateurs de la fonction de rendu de toujours utiliser this.$scopedSlot et de ne plus se soucier de savoir si le slot transmis est limité ou non. Ceci est également cohérent avec 3.0 où tous les emplacements sont exposés en tant que fonctions (mais sur this.$slots place).

  • Aucun changement à l'utilisation de slot-scope , aucune introduction de nouvelle directive. Nous voulons minimiser les changements de syntaxe et laisser la casse potentielle à 3.0.

@yyx990803 Est- $slot fusionne toutes les portées d'emplacement externes, ou se réfère-t-il simplement à l'emplacement le plus proche ?

@Justineo pas de fusion, juste le plus proche.

En regardant l'utilisation des slots dans les applications auxquelles j'ai accès, il semble que le raccourci $slot ne serait pas réellement utilisable dans un peu plus de la moitié des cas sans le comportement de fusion des slots imbriqués. Le problème est que les composants de base sont généralement utilisés à la place des éléments HTML bruts, et ces composants ont souvent leurs propres emplacements. Par exemple:

<MapMarkers :markers="cities">
  <BaseIcon name="map-marker">
    {{ $slot.marker.name }}
  </BaseIcon>
</MapMarkers>

Dans l'exemple ci-dessus, il n'y a qu'un seul emplacement de portée, pour le composant <MapMarkers> . <BaseIcon> accepte un slot sans portée, mais parce qu'il accepte un slot du tout, $slot serait indéfini ou un objet vide sans le comportement de fusion, forçant un refactoring plus maladroit :

<MapMarkers :markers="cities">
  <template slot-scope="{ marker }">
    <BaseIcon name="map-marker">
      {{ marker.name }}
    </BaseIcon>
  </template>
</MapMarkers>

Voici donc mes soucis :

  • Au moins d'après mon propre échantillonnage, il semble que _la plupart_ des avantages de $slot pourraient être perdus s'il ne se réfère qu'à l'emplacement le plus à l'intérieur. En fait, cela pourrait même faire plus de mal que de bien, car il y a plus d'API pour les gens à apprendre.

  • Les bibliothèques d'interface utilisateur pourraient commencer à sur-utiliser les accessoires avec v-html où les emplacements seraient plus appropriés, en réponse aux plaintes des utilisateurs concernant l'impossibilité d'utiliser $slot .

  • Les utilisateurs peuvent commencer à éviter les composants de base dans certains cas, pour conserver une utilisation plus élégante des slots, ce qui conduit à un code moins maintenable.

@yyx990803 Votre principale préoccupation avec la fusion de $slot -elle qu'il serait difficile de dire d'où proviennent les données d'emplacement de composant ? Si tel est le cas, il peut être utile de savoir que les seuls cas où les applications auxquelles j'ai accès ont utilisé des emplacements à portée imbriquée étaient pour les composants de fournisseur d'état, ce qui, comme je l'ai mentionné plus tôt, s'est avéré être un anti-modèle. Donc, en particulier avec des propriétés de slot bien nommées, je pense que les cas d'ambiguïté réelle dans des cas d'utilisation légitimes seraient très rares. Et avec le comportement de fusion automatique, les utilisateurs pourraient décider eux-mêmes s'ils ont besoin de plus d'explicitation - et quand ils le font, c'est pour cela que slot-scope existerait.

Cependant, en guise de compromis, voici une alternative potentielle : et si au lieu de fusionner les emplacements de portée imbriqués, nous avions $slot référence à l'emplacement le plus proche _qui a fourni une portée_ ? Cela pourrait éliminer les cas les plus ambigus, tout en répondant à mes préoccupations. Les pensées?

La principale raison d'éviter la fusion est le manque d'explicite : juste en lisant le modèle, vous ne savez vraiment pas quelle propriété $slot provient de quel composant fournisseur. Cela nécessite que vous soyez pleinement conscient des détails de mise en œuvre de chaque composant que vous examinez pour être sûr de ce qui se passe, et cela ajoute beaucoup de charge mentale. Cela peut sembler bien au moment où vous l'écrivez parce que vous avez tout le contexte dans votre esprit à ce moment-là, mais cela rend le code beaucoup plus difficile à comprendre pour un futur lecteur (que ce soit vous ou un autre membre de l'équipe).

$slot fait référence à l'emplacement le plus proche qui a fourni une portée

Je ne pense pas que cela fonctionnerait. Cela conduit au même problème : vous auriez besoin de connaître les détails d'implémentation des composants pour être sûr de ce qui se passe. Je dirais que c'est encore plus implicite et potentiellement déroutant que la fusion.


À long terme, je pense que la meilleure solution est de changer la sémantique de slot-scope afin qu'elle devienne un moyen d'aliaser/déstructurer $slot . Donc votre exemple ressemblerait à :

<MapMarkers :markers="cities" slot-scope="{ marker }">
  <BaseIcon name="map-marker">
    {{ marker.name }}
  </BaseIcon>
</MapMarkers>

Cela élimine le besoin de fusionner, indique clairement quelles variables proviennent de quel fournisseur et n'est pas trop détaillé. (Je pense que c'est ce que nous allons probablement faire dans 3.0)

Le point délicat dans lequel nous nous trouvons actuellement est que nous avons autorisé l'utilisation de slot-scope sur des non-modèles, et maintenant cela nous empêche de l'utiliser sur le composant lui-même.

Changer sa sémantique en 3.0 pourrait également conduire à beaucoup de confusion et de douleur de migration.

Peut-être pouvons-nous contourner le problème while en introduisant une nouvelle propriété, slot-alias , qui fait exactement ce qu'elle dit - aliaser $slot vers autre chose. Contrairement à slot-scope , il ne peut être utilisé que sur le composant lui-même ou sur un conteneur d'emplacement de modèle (cela signifie que cela fonctionnerait exactement de la même manière avec slot-scope lorsqu'il est utilisé sur un <template> ) :

Emplacements par défaut imbriqués :

<MapMarkers :markers="cities" slot-alias="{ marker }">
  <BaseIcon name="map-marker">
    {{ marker.name }}
  </BaseIcon>
</MapMarkers>
<foo slot-alias="foo">
  <bar slot-alias="bar">
    <baz slot-alias="baz">
      {{ foo }} {{ bar }} {{ baz }}
    </baz>
  </bar>
</foo>

Emplacements nommés sans imbrication :

Le seul cas où cela devient forcément verbeux, est nommé slots avec imbrication :

<foo>
  <template slot="a" slot-alias="a">
     <bar slot-alias="b">
        {{ a }} {{ b }}
     </bar>
  </template>
</foo>

(Cela peut en fait être moins détaillé en utilisant à la fois slot-scope ET slot-alias , mais je pense que cela peut être assez déroutant. Je suis en faveur de la dépréciation totale de slot-scope )

<foo>
   <bar slot="a" slot-scope="a" slot-scope="b">
      {{ a }} {{ b }}
   </bar>
</foo>

Enfin, nous pouvons même lui donner un raccourci, () (puisque dans JSX, les accessoires de rendu sont généralement des fonctions fléchées qui commencent par () ):

<MapMarkers :markers="cities" ()="{ marker }">
  <BaseIcon name="map-marker">
    {{ marker.name }}
  </BaseIcon>
</MapMarkers>
<foo ()="foo">
  <bar ()="bar">
    <baz ()="baz">
      {{ foo }} {{ bar }} {{ baz }}
    </baz>
  </bar>
</foo>

Comparez ce qui précède à l'équivalent JSX :

<foo>
  {foo => (
    <bar>
      {bar => (
        <baz>
          {baz => `${foo} ${bar} ${baz}`}
        </baz>
      )}
    </bar>
  )}
</foo>

Fermeture puisque le design est désormais très différent de ce qui était proposé à l'origine. Ouverture d'un nouveau fil à la place.

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