Vue: [Abandonado] RFC: simplificar el uso de ranuras con alcance

Creado en 11 dic. 2018  ·  36Comentarios  ·  Fuente: vuejs/vue

Este es un seguimiento de https://github.com/vuejs/vue/issues/7740#issuecomment -371309357

Racional

Problemas con el uso actual de la ranura con alcance:

  • Detallado si se usa <template slot-scope>
  • Limitado a un elemento / componente está usando slot-scope directamente en el elemento de la ranura.

Propuesta

Introduzca una nueva directiva v-scope , que solo se puede usar en componentes:

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

Funcionaría de la misma manera que slot-scope para la ranura con alcance predeterminado para <comp> (con <comp> proporcionando el valor del alcance). Por lo que también funciona con la deconstrucción:

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

Por qué una nueva directiva

Creo que el equipo discutió brevemente lo que propuse en https://github.com/vuejs/vue/issues/7740#issuecomment -371309357 en Slack hace algún tiempo, pero ya no pude encontrar el registro de chat. Aquí está el razonamiento detrás de una nueva directiva:

  • slot-scope se introdujo como un atributo especial en lugar de una directiva (atributos que comienzan con v- prefijo) porque slot es un atributo y queríamos mantener la coherencia de los atributos relacionados con el espacio. . slot se introdujo a su vez como un atributo no directivo porque queremos que el uso refleje el uso real de la ranura en el estándar Shadow DOM. Pensamos que sería mejor evitar tener nuestro propio paralelo v-slot cuando hay algo que es conceptualmente igual en el estándar.

  • Originalmente, slot-scope fue diseñado para ser utilizado solo en elementos <template> que actúan como contenedores abstractos. Pero eso fue detallado, por lo que presentamos la capacidad de usarlo directamente en un elemento de la ranura sin el envoltorio <template> . Sin embargo, esto también hace que sea imposible permitir el uso de slot-scope directamente en el propio componente, ya que generaría ambigüedad como se ilustra aquí .

  • Pensé en agregar modificadores o prefijos especiales a slot-scope para que podamos usarlo en un componente directamente para indicar que el contenido de su ranura debe tratarse como la ranura de ámbito predeterminada, pero ni un modificador ni un prefijo como $ parecen ser la opción correcta. El modificador por diseño solo debe aplicarse a las directivas, mientras que la nueva sintaxis especial para un caso de uso único es inconsistente con todo el diseño de sintaxis.

  • Durante mucho tiempo hemos evitado agregar más directivas, parte de la sintaxis de la plantilla es algo que queremos mantener lo más estable posible, parte de esto es que queremos mantener las directivas centrales al mínimo y solo hacer cosas que los usuarios no pueden hacer fácilmente en la tierra de los usuarios. Sin embargo, en este caso, el uso de ranuras de alcance es lo suficientemente importante, y creo que una nueva directiva puede justificarse para hacer que su uso sea significativamente menos ruidoso.

Preocupaciones

  • La expresión aceptada por v-scope es diferente de la mayoría de las otras directivas: espera un nombre de variable temporal (que también puede ser una deconstrucción), pero no sin precedencia: actúa como la parte de alias de v-for . Entonces, conceptualmente v-scope cae en el mismo campo con v-for como directiva estructural que crea variables temporales para su ámbito interno.

  • Esto rompería el código del usuario si el usuario tiene una directiva personalizada llamada v-scope y se usa en un componente.

    • Dado que las directivas personalizadas en v2 se centran principalmente en manipulaciones DOM directas, es relativamente raro ver directivas personalizadas utilizadas en componentes, más aún para algo que se llama v-scope , por lo que el impacto debería ser mínimo.

    • Incluso en el caso de que realmente suceda, es fácil de manejar simplemente cambiando el nombre de la directiva personalizada.

discussion intend to implement

Comentario más útil

Rompiendo el problema

Al intentar sintetizar, parece que queremos una solución que:

  • Reducir la repetición necesaria para acceder a los datos desde las ranuras con alcance.
  • Evite dar a entender que los datos de la ranura predeterminada están disponibles en ranuras con nombre.
  • minimizar la cantidad de API nuevas que tenemos que introducir
  • no reinvente las API donde la especificación de componentes web ya tiene una solución aceptable
  • Sea explícito, para evitar confusiones sobre el origen de los datos.

¡Podría tener una solución que aborde todos estos! 🤞 Con $event para v-on , tenemos un precedente de proporcionar expresiones que se ejecutan dentro de una función implícita con un argumento con nombre. La gente parece disfrutar de esa conveniencia y no veo mucha confusión causada por ella, así que tal vez deberíamos seguir ese ejemplo para las tragamonedas con alcance.

Solución propuesta

En lugar de usar v-scope , podríamos hacer que el alcance de la ranura esté disponible como $slot . Por ejemplo:

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

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

Para el contexto, la plantilla secundaria podría verse así:

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

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

Cuando las ranuras están anidadas, el objeto $slot fusionaría los datos de las ranuras, y las ranuras más internas tendrían prioridad. Por ejemplo, en:

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

la fusión podría verse así:

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

Es posible que se esté preocupando / preguntándose cómo manejar la superposición de espacios de nombres y, en el 99,9% de los casos, realmente no creo que sea un problema. Por ejemplo:

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

En el ejemplo anterior, $slot.user siempre tendrá el valor correcto, a pesar de los alcances de anidamiento. Sin embargo, a veces realmente necesitará acceso a ambas propiedades al mismo tiempo, como en:

<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>

Para estos casos extremos raros, los usuarios aún pueden usar slot-scope con su comportamiento actual como una trampilla de escape . Pero v-scope aún sería innecesario, porque si lo entiendo correctamente, su propósito sería simplificar los casos de uso más comunes, que el objeto $slot ya habría logrado sin causar los mismos problemas.

Ventajas

  • Cuando un usuario desea acceder a los datos proporcionados por una ranura, el único texto estándar es $slot , que es tan breve como puede ser sin dejar de ser explícito. Es inmediatamente obvio que los datos provienen de una ranura, sin necesidad de buscar en el componente para ver dónde está definida una propiedad.

  • Para saber a qué datos pueden acceder, los usuarios solo tienen que pensar en qué espacios se representará el contenido . Creo que con v-scope , habría mucha confusión si la gente asumiera que funciona como v-for , porque esa es la única otra directiva que define las propiedades de contexto con alcance.

  • Los usuarios no tienen que estar familiarizados y cómodos con la desestructuración para usar ranuras con alcance , lo que reduce la curva de aprendizaje.

  • En algunos casos, especialmente con muchas ranuras anidadas que proporcionan estado, no será obvio de qué componente proviene el estado y los usuarios tendrán que llegar a slot-scope nuevamente. Esto puede parecer una desventaja, pero cuando veo ranuras con alcance anidado, casi siempre es para el patrón de proveedor estatal, que creo firmemente que es un anti-patrón. Este es mi razonamiento . Por lo tanto,

  • El área de superficie de la API se reduce drásticamente , porque la mayoría de los desarrolladores de Vue solo tendrán que recordar $slot , que es solo un objeto.

  • Al usar una propiedad con el prefijo $ , estamos construyendo la API de la ranura del componente web de una manera que deja en claro que $slot es una cosa de Vue.

  • Actualmente, las bibliotecas suelen hacer algo como <slot v-bind="user" /> para que sus usuarios puedan guardar algunos caracteres con slot-scope="user" lugar de slot-scope="{ user } . Parece elegante a primera vista, pero he llegado a experimentarlo como un anti-patrón. El problema surge cuando el componente quiere exponer datos _que no sean_ el usuario. Luego tienen dos opciones: hacer un cambio radical en su API o forzar esta nueva propiedad en el objeto user , aunque puede tener muy poco que ver con el usuario. Tampoco es una gran opción. Afortunadamente, $slot eliminaría la tentación de hacer que los componentes sean menos preparados para el futuro , porque mientras que $slot aún es más corto que $slot.user , se pierde un contexto importante al asignar un alias al usuario como $slot .

Desventajas

  • En algunas ranuras de ámbito anidado, hay algunos casos extremos en los que no será obvio de qué componente provienen algunos datos. Sin embargo, en estos casos, los usuarios aún pueden alcanzar slot-scope cuando necesitan la máxima claridad, así que no creo que sea un gran problema. Además, como mencioné anteriormente, creo que es muy probable que el usuario se esté disparando en el pie con componentes del proveedor estatal si tiene este problema en primer lugar.

Todos 36 comentarios

Se ve bien. Estaba pensando en usar el argumento para proporcionar un nombre de ranura, pero esto permitiría usar varias directivas v alcance en el mismo componente y, por lo tanto, reutilizar el contenido proporcionado al componente. Lo cual no estoy seguro de si es útil o si podría dar lugar a problemas.

En general, esto mejorará el uso de la API de componentes sin renderizado, que se están utilizando más con el tiempo 🙌

Si lo entiendo correctamente, v-scope solo sirve para un caso de uso. En lugar de escribir:

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

Podemos escribir:

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

En este caso, redujo bastante el ruido, pero parece ser el único caso. Se siente como una especie de exageración introducir una nueva directiva (que funciona de manera bastante diferente a otras directivas, incluso desde v-for porque funciona solo en componentes y se basa en la lógica <slot> subyacente). Otra preocupación es que cuando busco v-scope en este repositorio , las dos únicas ocurrencias están discutiendo sobre la reducción del alcance de datos de un subárbol de plantilla (https://github.com/vuejs/vue/issues/5269 # issuecomment-288912328, https://github.com/vuejs/vue/issues/6913), como cómo funciona with en JavaScript.

Me gusta. Una pregunta que me queda sería cómo queremos tratar con las ranuras con nombre. Si queremos permitir que las ranuras con nombre utilicen la misma directiva, también deberíamos permitirla en <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>

Podríamos aprovechar esta oportunidad para desaprobar sot-scope y eliminarlo en Vue 3, reemplazándolo con v-scope

Creo que definitivamente no deberíamos terminar con dos conceptos diferentes ( v-scope directiva y slot-scope atributo) para lo mismo.

Nota al margen: ahora, mirando eso, la gente podría tener la impresión de la jerarquía de elementos y
directivas, que pueden acceder a otherScope y scope en la ranura nombrada. Podría ser una desventaja.

@LinusBorg para el nombre, un argumento podría hacer v-scope:slotName . Creo que el punto de permitir esto solo en los componentes es reemplazar lo que dijo @Justineo (https://github.com/vuejs/vue/issues/9180#issuecomment-446168296)

Nota al margen: ahora, mirando eso, la gente podría tener la impresión de la jerarquía de elementos y
directivas, que pueden acceder a otherScope y scope en la ranura nombrada. Podría ser una desventaja.

Eso funciona, ¿verdad? 🤔 Estoy bastante seguro de que he anidado los alcances de las tragamonedas

Eso funciona, ¿verdad? 🤔 Estoy bastante seguro de que he anidado los alcances de las tragamonedas

Sin embargo, no los anidaba, son ranuras de hermanos. Definí el alcance de la ranura predeterminada con v-scope en el componente, y la ranura nombrada (que es una ranura hermana ) con <template slot="someName">
¿Ver? Es confuso ^^

@LinusBorg Creo que el uso sería confuso. Creo que conceptualmente es:

  • Cuando se usa v-scope en el componente directamente, significa que solo está usando la ranura predeterminada.
  • Si desea usar ranuras con nombre ... aún necesita usar slot + slot-scope .

Estoy de acuerdo en que tener v-scope y slot-scope podría ser inconsistente / confuso, especialmente para los nuevos usuarios que no saben cómo llegamos al diseño actual.

Rompiendo el problema

Al intentar sintetizar, parece que queremos una solución que:

  • Reducir la repetición necesaria para acceder a los datos desde las ranuras con alcance.
  • Evite dar a entender que los datos de la ranura predeterminada están disponibles en ranuras con nombre.
  • minimizar la cantidad de API nuevas que tenemos que introducir
  • no reinvente las API donde la especificación de componentes web ya tiene una solución aceptable
  • Sea explícito, para evitar confusiones sobre el origen de los datos.

¡Podría tener una solución que aborde todos estos! 🤞 Con $event para v-on , tenemos un precedente de proporcionar expresiones que se ejecutan dentro de una función implícita con un argumento con nombre. La gente parece disfrutar de esa conveniencia y no veo mucha confusión causada por ella, así que tal vez deberíamos seguir ese ejemplo para las tragamonedas con alcance.

Solución propuesta

En lugar de usar v-scope , podríamos hacer que el alcance de la ranura esté disponible como $slot . Por ejemplo:

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

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

Para el contexto, la plantilla secundaria podría verse así:

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

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

Cuando las ranuras están anidadas, el objeto $slot fusionaría los datos de las ranuras, y las ranuras más internas tendrían prioridad. Por ejemplo, en:

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

la fusión podría verse así:

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

Es posible que se esté preocupando / preguntándose cómo manejar la superposición de espacios de nombres y, en el 99,9% de los casos, realmente no creo que sea un problema. Por ejemplo:

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

En el ejemplo anterior, $slot.user siempre tendrá el valor correcto, a pesar de los alcances de anidamiento. Sin embargo, a veces realmente necesitará acceso a ambas propiedades al mismo tiempo, como en:

<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>

Para estos casos extremos raros, los usuarios aún pueden usar slot-scope con su comportamiento actual como una trampilla de escape . Pero v-scope aún sería innecesario, porque si lo entiendo correctamente, su propósito sería simplificar los casos de uso más comunes, que el objeto $slot ya habría logrado sin causar los mismos problemas.

Ventajas

  • Cuando un usuario desea acceder a los datos proporcionados por una ranura, el único texto estándar es $slot , que es tan breve como puede ser sin dejar de ser explícito. Es inmediatamente obvio que los datos provienen de una ranura, sin necesidad de buscar en el componente para ver dónde está definida una propiedad.

  • Para saber a qué datos pueden acceder, los usuarios solo tienen que pensar en qué espacios se representará el contenido . Creo que con v-scope , habría mucha confusión si la gente asumiera que funciona como v-for , porque esa es la única otra directiva que define las propiedades de contexto con alcance.

  • Los usuarios no tienen que estar familiarizados y cómodos con la desestructuración para usar ranuras con alcance , lo que reduce la curva de aprendizaje.

  • En algunos casos, especialmente con muchas ranuras anidadas que proporcionan estado, no será obvio de qué componente proviene el estado y los usuarios tendrán que llegar a slot-scope nuevamente. Esto puede parecer una desventaja, pero cuando veo ranuras con alcance anidado, casi siempre es para el patrón de proveedor estatal, que creo firmemente que es un anti-patrón. Este es mi razonamiento . Por lo tanto,

  • El área de superficie de la API se reduce drásticamente , porque la mayoría de los desarrolladores de Vue solo tendrán que recordar $slot , que es solo un objeto.

  • Al usar una propiedad con el prefijo $ , estamos construyendo la API de la ranura del componente web de una manera que deja en claro que $slot es una cosa de Vue.

  • Actualmente, las bibliotecas suelen hacer algo como <slot v-bind="user" /> para que sus usuarios puedan guardar algunos caracteres con slot-scope="user" lugar de slot-scope="{ user } . Parece elegante a primera vista, pero he llegado a experimentarlo como un anti-patrón. El problema surge cuando el componente quiere exponer datos _que no sean_ el usuario. Luego tienen dos opciones: hacer un cambio radical en su API o forzar esta nueva propiedad en el objeto user , aunque puede tener muy poco que ver con el usuario. Tampoco es una gran opción. Afortunadamente, $slot eliminaría la tentación de hacer que los componentes sean menos preparados para el futuro , porque mientras que $slot aún es más corto que $slot.user , se pierde un contexto importante al asignar un alias al usuario como $slot .

Desventajas

  • En algunas ranuras de ámbito anidado, hay algunos casos extremos en los que no será obvio de qué componente provienen algunos datos. Sin embargo, en estos casos, los usuarios aún pueden alcanzar slot-scope cuando necesitan la máxima claridad, así que no creo que sea un gran problema. Además, como mencioné anteriormente, creo que es muy probable que el usuario se esté disparando en el pie con componentes del proveedor estatal si tiene este problema en primer lugar.

Creo que una propiedad con prefijo $ debería ser coherente dentro de una instancia completa de Vue. No estoy seguro de si causará confusión al inyectar implícitamente un $slot en cada ranura y en realidad representan un argumento local dentro de estos ámbitos de ranura. Es una especie de convención (según mi observación) que las propiedades con prefijo $ representan algo disponible para cada instancia de Vue, por lo que se pueden usar en cualquier lugar, incluidos los ganchos del ciclo de vida, los métodos y las funciones de renderización. Agregar este $slot romperá esta convención.

@chrisvfritz esta es una propuesta interesante, pero se basa en la unificación de ranuras normales y ranuras con alcance (por lo que tal vez sea algo que se pueda considerar en la v3). El mayor problema con esto es que el contenido de la ranura debe estar disponible en el componente secundario $slots o $scopedSlots ? La única pista es la presencia de $slot en algún lugar de las expresiones (puede estar en cualquier parte del árbol), que no es tan explícito como slot-scope (que solo puede estar en la raíz de la ranura). Aunque técnicamente podemos detectar esto en el compilador, para el usuario la ranura se moverá de $slots a $scopedSlots siempre que el usuario comience a usar $slot en la plantilla ...

¿Tendrá esto algún impacto importante en los usuarios de JSX?

@donnysim no, esto no afecta a JSX de ninguna manera.

Creo que una propiedad con prefijo $ debería ser coherente dentro de una instancia completa de Vue. No estoy seguro de si causará confusión al inyectar implícitamente un $slot en cada ranura y en realidad representan un argumento local dentro de estos ámbitos de ranura.

@Justineo Al $event y nadie parece quejarse, por lo que ya existe un precedente y tenemos evidencia de que los usuarios normalmente no están confundidos y realmente disfrutan de la conveniencia.

El mayor problema con esto es si el contenido de la ranura debe estar disponible en el componente secundario $slots o $scopedSlots ?

@ yyx990803 ¡ Gran pregunta! Tengo una idea que podría funcionar. ¿Qué pasa si en los casos en que es ambiguo (ranuras anidadas que usan $slot ), simplemente compilamos todas las ranuras en una ranura con alcance, pero también hacemos que todos los $scopedSlots estén disponibles en $slots como captadores? Por ejemplo, algo como:

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

De esa forma, en el caso de:

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

Podríamos compilar algo como:

// 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))]
                },
              },
            ]),
          }),
        ]
      },
    },
  ]),
}) 

Y no importa si foo proviene de <A> o <B> , porque dentro de esos componentes funcionarán tanto this.$slots.default como this.$scopedSlots.default(someData) .

Un par de advertencias:

  • En los casos con ranuras anidadas y solo algunas de ellas tienen realmente un alcance, las funciones de renderización compiladas a partir de plantillas que usan $slot serían un poco más grandes que si se usara slot-scope . Sin embargo, no de manera muy significativa, así que creo que está bien.

  • No puedo pensar en un ejemplo real, pero puede haber casos extremos en los que un usuario esté iterando sobre $slots / $scopedSlots y tener propiedades nuevas o duplicadas podría resultar en un comportamiento inesperado. He iterado dinámicamente sobre $slots y $scopedSlots antes para habilitar algunos patrones interesantes, pero este cambio no afectaría ninguno de los casos de uso con los que me he encontrado.

¿Pensamientos?

hacemos esto por $event y nadie parece quejarse

@chrisvfritz Oh, me perdí la cosa $event . Aunque es claramente una excepción a la convención (solía creer que lo es 😅), $event solo está disponible en un alcance muy limitado (solo dentro de los literales de atributo v-bind y sin anidamiento).

Y por proxy $scopedSlots en $slots :

Solía ​​creer que las tragamonedas y las tragamonedas con alcance son conceptos diferentes y Vue tiene un uso separado para ellos, lo que significa que puedo tener una tragamonedas y una tragamonedas con alcance compartiendo el mismo nombre pero con diferentes propósitos. Pero más tarde descubrí que Vue en realidad recurre a la ranura con el mismo nombre cuando la ranura con alcance especificado no está disponible. Para mí, esto significa que deberíamos considerarlos como lo mismo, pero como tenemos $slots y $scopedSlots disponibles en cada instancia, siempre podemos usarlos en funciones de renderizado de una manera más flexible / inesperada. , como usar un espacio predeterminado para anular todos los elementos de la lista y un espacio de ámbito predeterminado para anular un solo elemento. En realidad, esto no se recomienda (no hemos sugerido el uso recomendado de esto en nuestros documentos y la guía de estilo AFAIK), ya que es probable que los combinemos en un solo concepto de ranura en 3.0, pero hacerlo en 2.x fallará. algún uso permitido desde hace bastante tiempo.

Es probable que los fusionemos en un solo concepto de ranura en 3.0, pero hacerlo en 2.x romperá algunos usos permitidos desde hace bastante tiempo.

@Justineo Podría estar malinterpretando, pero no estoy sugiriendo fusionarlos en 2.x ya, solo extendiendo el comportamiento de respaldo actual. El caso de uso que describió, donde tanto $slots como $scopedSlots usan el mismo espacio de nombres, aún sería posible porque el comportamiento de slot-scope permanecería sin cambios. Por ejemplo:

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

seguiría funcionando exactamente igual. ¿Tiene sentido?

@chrisvfritz

No estaba hablando de fusionarlos en 2.x. Estaba diciendo que si haces esto:

hacer que todos los $scopedSlots estén disponibles en $slots como captadores

Ya no puede distinguir $slots.foo de $scopedSlots.foo y usarlos por separado en las funciones de renderizado.

No puede distinguir $ slots.foo de $ scopedSlots.foo y usarlos por separado en las funciones de renderizado.

Honestamente, estaría feliz si todo (slots y scopedSlots) fuera accesible en scopedSlots como funciones, solo significa que no hay más comprobaciones inútiles sobre qué ranura renderizar. Realmente no hace ninguna diferencia desde la perspectiva de los componentes qué tipo de ranura usa el desarrollador. Procedente de un usuario mixto de JSX y sintaxis de plantilla.

@donnysim Sí, estoy de acuerdo contigo en que deberían fusionarse. Expliqué por qué tengo tal preocupación en https://github.com/vuejs/vue/issues/9180#issuecomment -447185512. Se trata de compatibilidad con versiones anteriores.

Ya no puede distinguir $slots.foo de $scopedSlots.foo y usarlos por separado en las funciones de renderizado.

@Justineo Usted _ puede_ en realidad, siempre que procesemos $slots primero y no reemplacemos las ranuras ya definidas. Vea la implementación de ejemplo que publiqué anteriormente:

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

Considere el siguiente ejemplo:

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

Si el usuario del componente solo pasó vm.$scopedSlots.default , debería recorrer this.items y representar item s en la ranura delimitada. Pero ahora que vm.$scopedSlot.default es ahora la ranura con ámbito y existe, se invocará la ranura con ámbito en lugar de la ranura.

@Justineo Creo que este caso de uso sería muy raro, pero el patrón aún sería posible cambiando:

if (!this.$slots.default) {

para:

if (this.$scopedSlots.default) {

o, si el usuario a veces proporciona un espacio con ámbito que se supone que debe ignorarse por alguna razón:

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

Sin embargo, todavía no lo consideraría un cambio rotundo. Primero, nunca hemos documentado / recomendado el uso de ranuras y ranuras con alcance del mismo nombre, por lo que nunca fue parte del contrato público. En segundo lugar, como mencionó, los espacios con alcance en las plantillas ya se remontan a un espacio sin alcance del mismo nombre, lo que sirve como una buena evidencia histórica de que nunca pretendimos que el límite con alcance / sin alcance se usara de esta manera.

¿Tiene sentido? Además, ¿ha visto algún caso de uso real para reutilizar nombres de tragamonedas? ¿O puedes pensar en alguno? No puedo, pero si _hay_ patrones útiles y únicos que habilita, sería bueno aprender sobre ellos ahora porque también influiría en nuestra decisión de fusionarlos en Vue 3.

Creo que las ranuras con ámbito y las ranuras con el mismo nombre para diferentes propósitos son
una mala idea. Usé esto en la versión pré de Vue prometida y la eliminé.
porque era confuso. Y ahora tengo que verificar tanto la ranura predeterminada
y ranura con alcance para que el desarrollador pueda proporcionar una ranura sin consumir los datos.
Y si mal no recuerdo, no puede tener el mismo nombre cuando usa
plantillas.

@chrisvfritz

No estaba diciendo que el patrón fuera útil y debería ser compatible. De hecho, puede causar confusión. Solo estaba diciendo que es posible romper el código existente. Nunca hemos documentado compartir el mismo nombre entre ranuras y ranuras con alcance, pero nunca sugerimos a los usuarios que tampoco lo hagan. Y la lógica de respaldo en las plantillas no está documentada. Puede que no sea intencional, pero al menos yo mismo solía creer que es un uso legítimo hasta que aprendí la lógica de respaldo en las plantillas ...

@posva ¡ Gracias por compartir tu experiencia!

@Justineo En su opinión, ¿cree que este caso extremo causaría demasiado dolor a los usuarios, o es aceptable por la conveniencia de mejorar la API de ranura con alcance en Vue 2.x?

Esto se movió a "Todo" - ¿Tenemos un consenso sobre qué implementar?

He estado pensando en esto la semana pasada. Me gusta la propuesta de variable $slot @chrisvfritz y estaba tratando de resolver los bloqueadores de implementación. Esto es lo que creo que es factible:

  • Todas las ranuras se compilan como funciones (como en 3.0), por lo que no más ranuras estáticas (esto conduce a un seguimiento de actualización de componentes más preciso)
  • $scopedSlots expondrá cada ranura como una función
  • $slots expondrá cada ranura como resultado de llamar a la función correspondiente con un objeto vacío.

Entonces lo siguiente sería equivalente:

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

Con los cambios internos anteriores, $slots y $scopedSlots están esencialmente unificados, ¡pero deberían ser compatibles con versiones anteriores! También debería facilitar la migración a 3.0.

Algunas muestras de uso que utilizan la variable propuesta $slot :

<!-- 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>

No creo que la fusión de ámbitos anidados sea una buena idea. Creo que es mejor ser explícito cuando se trata de anidación (es decir, siempre use slot-scope si tiene ranuras de ámbito anidado):

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

En su último ejemplo, ¿a qué ranura se refiere $slot ?

@Akryum es { ...foo, ...bar } .

@Akryum @Justineo hmm Puedo decir que esto podría ser confuso ... hicimos slot-scope utilizables en el elemento raíz de una ranura , es decir, aquí foo es el alcance de la ranura proporcionado por <foo/> , bar es proporcionado por <bar/> y $slot es proporcionado por <baz/> ... ahora creo que fue un error permitir tal uso, porque el uso más intuitivo en este caso sería algo como esto:

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

esto puede ser confuso

un uso más intuitivo en este caso sería algo como esto

Totalmente de acuerdo. Además, podemos seguir usando la desestructuración.
Por ejemplo:
`` 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>

Cerrado por 7988a554 y 5d52262f

Resumen:

  • Admite la variable $slot en todas las ranuras. (La presencia de $slot hace que la ranura se compile como una ranura con ámbito).

  • Todas las ranuras, incluidas las ranuras normales, ahora están expuestas en this.$scopedSlots como funciones. Esto significa que asumiendo que this.$slots.default existe, this.$scopedSlots.default() devolverá su valor. Esto permite que los usuarios de la función de renderizado usen siempre this.$scopedSlot y ya no se preocupen por si la ranura que se pasa tiene un alcance o no. Esto también es consistente con 3.0 donde todas las ranuras se exponen como funciones (pero en su lugar en this.$slots ).

  • Sin cambios en el uso de slot-scope , sin introducción de una nueva directiva. Queremos que los cambios de sintaxis sean mínimos y dejar la posible rotura en 3.0.

@ yyx990803 ¿ $slot fusiona todos los ámbitos de las ranuras exteriores o simplemente se refiere a la ranura más cercana?

@Justineo sin fusionar, solo lo más cercano.

Al observar el uso de ranuras con alcance en las aplicaciones a las que tengo acceso, parece que la abreviatura $slot no se podría usar en poco más de la mitad de los casos sin el comportamiento de fusión de ranuras anidadas. El problema es que los componentes básicos se utilizan normalmente en lugar de los elementos HTML sin procesar, y estos componentes suelen tener sus propios espacios. Por ejemplo:

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

En el ejemplo anterior, solo hay un espacio de ámbito único para el componente <MapMarkers> . <BaseIcon> acepta un espacio sin ámbito, pero debido a que acepta un espacio en absoluto, $slot no estaría definido o sería un objeto vacío sin el comportamiento de fusión, lo que obligaría a refactorizar al más torpe:

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

Así que aquí están mis preocupaciones:

  • Al menos según mi propia muestra, parece que la mayor parte del beneficio de $slot podría perderse si solo se refiere a la ranura más interna. De hecho, incluso podría hacer más daño que bien, ya que hay más API para que la gente aprenda.

  • Las bibliotecas de interfaz de usuario pueden comenzar a usar en exceso accesorios con v-html donde las ranuras serían más apropiadas, en respuesta a las quejas de los usuarios sobre no poder usar $slot .

  • Los usuarios pueden comenzar a evitar los componentes básicos en algunos casos, para mantener el uso de las ranuras dentro del ámbito más elegante, lo que lleva a un código menos mantenible.

@ yyx990803 ¿Su principal preocupación con la fusión de $slot es que sería difícil saber de qué componentes provienen los datos de las ranuras? Si es así, puede ser útil saber que los únicos casos en los que las aplicaciones a las que tengo acceso utilizan ranuras de ámbito anidado fueron para componentes de proveedores estatales, que, como mencioné anteriormente, he encontrado que son un antipatrón. Entonces, especialmente con propiedades de tragamonedas bien nombradas, creo que los casos de ambigüedad real en casos de uso legítimos serían muy raros. Y con el comportamiento de fusión automática, los usuarios podrían decidir por sí mismos si necesitan más claridad, y cuando lo hagan, para eso existiría slot-scope .

Sin embargo, como compromiso, aquí hay una alternativa potencial: ¿qué pasaría si en lugar de fusionar ranuras con alcance anidado, tuviéramos $slot referirse a la ranura más cercana _que proporcionó un alcance_? Eso podría eliminar los casos con la mayor ambigüedad, sin dejar de abordar mis preocupaciones. ¿Pensamientos?

La razón principal para evitar la fusión es que no es explícito: con solo leer la plantilla, realmente no sabe qué propiedad $slot proviene de qué componente del proveedor. Esto requiere que esté plenamente consciente de los detalles de implementación de cada componente que está mirando para estar seguro de lo que está sucediendo, y eso agrega mucha carga mental. Puede sonar bien en el momento en que lo está escribiendo porque tiene el contexto completo en su mente en ese momento, pero hace que el código sea mucho más difícil de entender para un futuro lector (ya sea usted u otro miembro del equipo).

$ ranura se refiere a la ranura más cercana que proporcionó un alcance

No creo que eso funcione. Conduce al mismo problema: necesitaría conocer los detalles de implementación de los componentes para estar seguro de lo que está sucediendo. Yo diría que es incluso más implícito y potencialmente confuso que fusionar.


A largo plazo, creo que la mejor solución es cambiar la semántica de slot-scope para que se convierta en una forma de alias / desestructurar $slot . Entonces tu ejemplo se vería así:

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

Esto elimina la necesidad de fusionar, es explícito sobre qué variables provienen de qué proveedor y no es demasiado detallado. (Creo que esto es lo que probablemente vamos a hacer en 3.0)

El lugar incómodo en el que nos encontramos ahora es que permitimos que slot-scope se usara en no plantillas, y ahora eso nos impide usarlo en el componente en sí.

Cambiar su semántica en 3.0 también podría generar mucha confusión y problemas de migración.

Tal vez podamos eludir el problema while introduciendo una nueva propiedad, slot-alias , que hace exactamente lo que dice: alias $slot con otra cosa. A diferencia de slot-scope , solo se puede usar en el componente en sí o en un contenedor de ranura de plantilla (esto significa que funcionaría exactamente igual con slot-scope cuando se usa en un <template> ) :

Ranuras predeterminadas anidadas:

<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>

Ranuras con nombre sin anidamiento:

El único caso en el que inevitablemente se vuelve detallado, es ranuras nombradas con anidamiento:

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

(De hecho, esto puede ser menos detallado usando slot-scope Y slot-alias , pero creo que puede ser bastante confuso. Estoy a favor de desaprobar slot-scope completo)

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

Finalmente, incluso podemos darle una abreviatura, () (ya que en JSX los accesorios de renderización suelen ser funciones de flecha que comienzan con () ):

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

Compare lo anterior con el JSX equivalente:

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

Cierre ya que el diseño ahora es muy diferente de lo que se propuso originalmente. Abriendo un nuevo hilo en su lugar.

¿Fue útil esta página
0 / 5 - 0 calificaciones