Vue: API mejorada para los componentes de la interfaz de usuario, lo que reduce el texto estándar para el reenvío de atributos y eventos

Creado en 27 jun. 2017  ·  46Comentarios  ·  Fuente: vuejs/vue

¿Qué problema resuelve esta función?

Hay muchos casos en los que los atributos pasados ​​a un componente de Vue no deben agregarse al elemento raíz, sino a un subelemento. Por ejemplo, en este componente de la interfaz de usuario , se debe usar una cantidad increíble de accesorios para garantizar que los atributos se agreguen al elemento input , en lugar del contenedor div .

Además, a menudo es deseable exponer todos los oyentes de eventos en un elemento de formulario al padre, lo que también requiere mucho texto estándar si el elemento no es la raíz (en cuyo caso, el modificador .native puede resolver el problema ).

¿Cómo se ve la API propuesta?

EDITAR: Comience aquí para ponerse al día con la discusión.

Actualmente, de forma predeterminada, el elemento "expuesto" (al que se pueden agregar atributos arbitrarios) es siempre el elemento raíz. Se podría utilizar una nueva directiva para definir un elemento expuesto diferente. Algunas ideas para el nombre de la directiva:

  • v-expose (probablemente mi favorito personal)
  • v-expose-attrs (probablemente más claro, pero más largo)
  • v-main
  • v-primary

Si se agrega v-expose a un elemento, aceptará los atributos pasados ​​a su componente, y estos atributos __ ya no__ se pasarán al elemento raíz.

Otras características que pueden resultar agradables:

  • Si la directiva se define en varios elementos, los atributos se duplicarán en cada uno de ellos.
  • En el caso de que un elemento deba aceptar un subconjunto de atributos, v-expose podría aceptar una cadena o matriz de cadenas (por ejemplo, v-expose="class" o v-expose="['class', 'type', 'placeholder']" ). En este caso, estos atributos se agregarían al elemento (nuevamente, en lugar del elemento raíz), pero todos los demás atributos se agregarían al elemento raíz o al elemento (s) con un valor sin valor v-expose .
discussion feature request

Comentario más útil

@chrisvfritz ¿cómo funcionaría eso en las funciones de render?

Creo que tal vez sería mejor:

  • proporcionar una opción para deshabilitar la herencia automática de atributos para el nodo raíz
  • exponer esos atributos como $attributes , por ejemplo (nombrar tbd)
  • use v-bind para agregarlos donde quiera, como ya mostramos para hacer con $props :
v-bind="$attributes"

Eso tendría el beneficio adicional de trabajar prácticamente idéntico en las funciones JSX / render

Todos 46 comentarios

Hmm, no sé sobre eso, pero para componentes como ese, creo que podrías usar JSX o createElement para difundir accesorios.

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

Para nosotros esto sería genial. Envolvemos todas las entradas en una etiqueta con fines de estilo y ux. Estoy de acuerdo en que podríamos bajar a jsx en su lugar, pero las plantillas son mucho más fáciles de seguir para todos.

@Austio , desafortunadamente, esa es la recuperación de las plantillas ... espera ... ¿tal vez podríamos pensar en una forma de spread props en plantillas vue?

Personalmente, me gusta esta característica. Pero parece romper la consistencia del comportamiento de v-bind, como a veces todavía necesito vincular la propiedad class para el elemento raíz.

Entonces, ¿qué tal usar un par de directivas como getter y setter como:

Dentro del componente, defina un ancla v-expose :

<input v-expose="foo" />

Al usarlo:

<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 , eso se ve increíble, pero nuevamente, parece una extensión básica y con problemas potenciales como ¿cómo definirías @keyup.enter.prevent="myAction" ?

no puede simplemente <the-component :foo="{'@keyup.enter.prevent': myAction}"></the-component> , eso significa que tendría que mantener todos los modificadores como enter y prevent en el tiempo de ejecución (que es parte de vue-template-compiler Cajero automático)

@nickmessing

que parece una extensión básica

De lo que estamos hablando es de traer algo como propagación para los usuarios de plantillas.

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

@ es un shortland v-on, no significa prop (v-bind).

@jkzing , en el enlace de la descripción también hay muchos enlaces v-on

@nickmessing Um ... En cuanto a los enlaces v-on , es otro tema en mi opinión, como el burbujeo de eventos. 🤔

@jkzing , ese era el concepto completo de v-expose afaik, para hacer que todas las propiedades "vayan" a un determinado elemento en el componente

@nickmessing , No puedo estar seguro de la propuesta original, pero no creo que un oyente de eventos deba considerarse attribute .

@jkzing , probablemente no, pero considerando el ejemplo común de <my-awesome-text-input /> donde puede tener> 9000 accesorios diferentes, solo quiere que todos lleguen a su <input /> que está dentro de su componente personalizado sin una tonelada de código.

Yo personalmente uso v-bind="$props" o puede filtrarlos para excluir los accesorios que no desea aplicar. De esta forma, puede aplicar varios accesorios a la vez en una entrada. De hecho, v-expose podría ser útil porque para los componentes de envoltura, como las entradas, debe especificar todos esos accesorios html

Así que esto
https://github.com/almino/semantic-ui-vue2/blob/master/src/elements/Input.vue#L9
la caña se reducirá a v-bind="$props" o v-bind="filteredProps" donde filtrados Props podrían ser una propiedad calculada

@cristijora También estamos usando v-bind="someProps" . El problema con esta solución es que se agregarán propiedades excesivas como atributos HTML. Sería genial si v-bind= pudiera filtrar todas las propiedades que no son aceptadas por el componente. Con <component> dinámicos no sabemos qué accesorios filtrar en la propiedad calculada. Aunque es posible extraer options.props y usar lodash._pick .

¿Es esto realmente factible con una directiva?

@posva , no creo que esto funcione como una directiva per se, pero eso puede ser parte del motor de plantilla vue que hace algo como propagarse internamente + algo de propagación de eventos

@posva No

@chrisvfritz ¿tiene alguna idea sobre una API sobre cómo se usaría (especificando qué exponer y cómo agregar al niño)?

Pude ver que esto es similar en uso para proporcionar / inyectar concepto.

@Austio Puede que no esté entendiendo la pregunta, pero proporciono algunas ideas sobre la API en la publicación original.

Hola Chris, me refiero a pensamientos adicionales sobre el uso de similar para proporcionar inyección donde declaras lo que se puede exponer en el padre y luego lo usas en el niño.

Ah, ya veo. No estoy seguro de que sea necesario. La información ya se puede pasar a través de accesorios y espacios, e incluso se puede acceder a las propiedades privadas del padre con this.$parent , aunque creo que es mejor evitar ese patrón.

@Austio ¿Hay algún caso de uso en particular en el que esté pensando?

@chrisvfritz ¿cómo funcionaría eso en las funciones de render?

Creo que tal vez sería mejor:

  • proporcionar una opción para deshabilitar la herencia automática de atributos para el nodo raíz
  • exponer esos atributos como $attributes , por ejemplo (nombrar tbd)
  • use v-bind para agregarlos donde quiera, como ya mostramos para hacer con $props :
v-bind="$attributes"

Eso tendría el beneficio adicional de trabajar prácticamente idéntico en las funciones JSX / render

@LinusBorg Me gusta tu forma de pensar. 😄 Tu camino es mucho más intuitivo.

Como nota al margen, creo que con esta API en su lugar, la próxima versión principal de Vue podría incluso eliminar la herencia automática de atributos por completo, de modo que la comunicación entre componentes podría permanecer explícita en ambos lados.

Sería posible depreciar o eliminar este comportamiento, sí.

Si eso vale la pena, los cambios posiblemente necesarios en muchos componentes de las bibliotecas, etc., deben decidirse y deben discutirse con la comunidad, especialmente con los autores de la colección de IU.

A sobre la característica propuesta: esta información ya está disponible en componentes funcionales a través de context.data.attributes , por lo que esta característica daría básicamente la misma funcionalidad a los componentes de la instancia.

Sí exactamente. El objetivo principal que tengo en mente es simplificar el trabajo de los autores de componentes de la interfaz de usuario (tanto de terceros como internos). Actualmente hay muchos casos en los que es necesario algo como esto:

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

Una nueva propiedad $attributes podría acortarla a esto:

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

Aunque supongo que sería bueno tener alguna forma de exponer también los eventos. ¿Quizás una directiva v-on vacía podría reenviar todos los oyentes de eventos en el padre a este elemento?

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

O si al final hay varias preocupaciones que queremos agrupar, es posible que volvamos a algo como v-expose :

<input v-expose>

Esto se ha convertido en una discusión más amplia sobre cómo simplificar la construcción de componentes de la interfaz de usuario, en lugar de una solicitud de función específica, por lo que volveré a etiquetar este problema. 🙂

Llego tarde a este tema, pero también tengo algunas ideas.

v-bind Solución actual y desventajas

En primer lugar, ya uso y amo la función v-bind="propObject" (tan poderosa). Por ejemplo, bootstrap-vue tiene un componente de enlace interno que se usa en todas partes (botones, navegadores, listas desplegables, etc.). El componente pivota convirtiéndose en un ancla nativo frente a un enlace de enrutador basado en href frente a to y presencia de vm.$router , por lo que hay bastantes propiedades para pasar condicionalmente a cada uno de estos componentes.

Nuestra solución fue poner esos accesorios en un mixin y usar v-bind="linkProps" con un objeto calculado. Esto funciona muy bien, pero todavía es una gran sobrecarga agregar esa mezcla a _todos los demás componentes que usan el componente de enlace_.

v-expose Posibilidades usando v-bind

Personalmente, me gusta el concepto de v-expose , y tal vez podría funcionar como ranura predeterminada + ranuras con nombre, y luego usar modificadores para acceder a las ranuras de atributos con nombre.

El atributo predeterminado _ "ranura" _ siempre pasaría atributos al componente en sí (sin cambios), mientras que el componente podría especificar otros objetivos con nombre. Quizás algo como esto:

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

Dentro de MyComponent.vue :

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

Proxying de eventos

No tengo mucho que agregar aquí, excepto que .native es un modificador increíblemente poderoso. Me resolvió muchos problemas. Sin embargo, parece en gran parte desconocido para los desarrolladores de Vue (veo una buena cantidad de problemas de la biblioteca de la interfaz de usuario que se resuelven al exponer a los desarrolladores a esta función). Puse un PR en el sitio web para agregar más documentos y soporte de búsqueda en el sitio y potencialmente optimizado para la búsqueda de Google.

Partiendo de una discusión sobre la superficie de la API en otro tema, debo repetir que no soy un fanático de la idea v-expose . introduce otra "forma en que funcionan las cosas", y no funciona para JSX sin también implementar algo especial allí, etc.

Una cosa que respeto de la gente de React es su compromiso con una API delgada y con el uso de las funciones del lenguaje tanto como sea posible. Con ese espíritu, reutilizar un patrón que ya tenemos para los accesorios de los atributos parece mucho mejor que introducir otra abstracción.

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

Ahh, veo lo que estás diciendo ahora. ¡Y me gusta! ¿Está disponible actualmente? vm.$attributes sería la adición en su lugar?

Releyendo tus comentarios. Estoy rastreando ahora 👍

Sí, $attributes sería la adición.

Además, necesitaríamos una opción para desactivar el comportamiento predeterminado actual de aplicar atributos al elemento raíz, como esta:
`` `html

Esto podría convertirse en una configuración predeterminada en Vue 3.0 si luego decidimos hacer esto, lo que resultará en un cambio radical.

@LinusBorg ¿Qué piensas sobre cómo lidiar con el lado de los eventos? Para seguir la misma estrategia, supuse que también podríamos agregar una propiedad $listeners , que podría verse así:

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

Entonces quizás v-on podría aceptar un objeto, similar a v-bind . Entonces tendríamos:

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

Un problema que preveo es con los eventos input / change , ya que v-model funciona de manera ligeramente diferente para los componentes que para los elementos. Tampoco sé si querríamos $listeners y $nativeListeners . Supongo que si $listeners estuvieran disponibles, entonces el modificador .native podría estar obsoleto.

Además, con respecto a la opción applyComponentAttrsToRoot , quizás exposeRootEl sería un buen nombre, que cuando se establece en false , podría deshabilitar tanto los atributos aplicados automáticamente .native evento

También sería bueno poder deshabilitar esto para toda la aplicación a través de Vue.config , así como para un solo componente.

Recientemente tuve una idea similar sobre $listeners ; también está disponible en componentes funcionales a través de

context.data.listeners

Así que terminaríamos con $props , $attributes , $listeners que me suena bien.

También hay # 5578 pidiendo v-on="{...}" sintaxis de objeto como usé para $attributes , encajaría perfectamente.

Pero no estoy seguro del modificador .native. Para que esto funcione tanto con eventos de componentes como con oyentes nativos, la API terminaría siendo mucho más complicada y el uso es cuestionable, ya que un oyente de eventos nativo aplicado al elemento raíz aún detectaría el evento deseado burbujeando, por lo que podría no ser Será necesario asignarlo a un elemento específico de la plantilla.

En general, diría que para las bibliotecas de componentes de bajo nivel, las funciones de renderización deberían ser preferidas cuando las plantillas se vuelven incómodas para trabajar. Pero estoy de acuerdo en que lo siguiente sería valioso:

  1. Deshabilitar el comportamiento predeterminado de "aplicar automáticamente enlaces que no se encuentran en los accesorios como atributos del elemento raíz" (problema relacionado: ¿esto debería afectar también a los enlaces class y style ?)

  2. exponer una forma más fácil de "heredar" enlaces externos en el componente en un elemento interno que no es necesariamente la raíz. Idealmente con coherencia entre plantillas y funciones de renderizado.

ia kie like vue, herramientas simples

¡Solo quiero decir que el PR para esto en v2.4 es excelente! 👍

De nota de lanzamientos

La combinación de estos nos permite simplificar un componente como este en esto:

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

Parece agradable, pero eso no es del todo cierto, ya que este tipo de componentes están diseñados para funcionar con el modelo v y, hasta donde yo sé, el modelo v en el componente de envoltura no está funcionando bien. ¿Hay algún ejemplo de cómo reenviar el modelo v desde un componente de envoltura a una entrada, por ejemplo?
La forma más sencilla que he encontrado:
https://jsfiddle.net/60xdxh0h/2/

Tal vez con el componente funcional trabajando a lo largo de la plantilla sería más sencillo

este tipo de componentes están diseñados para funcionar con el modelo v y, hasta donde yo sé, el modelo v en el componente de envoltura no está funcionando bien.

¿Por qué piensas eso? v-model es solo azúcar de sintaxis para un accesorio y un detector de eventos, ambos estarán en $ attr / $ props y, por lo tanto, se pueden transmitir fácilmente.

Supongo que lo único que requeriría conocimiento de las opciones secundarias sería si el niño cambiara los valores predeterminados de model , eso es cierto.

Pero sería posible leerlos, dependiendo de las circunstancias.

Claro que es un azúcar sintáctico, pero quiero decir que podría ser confuso leer

La combinación de estos nos permite simplificar un componente como este en esto:

cuando en realidad se basa en el ejemplo https://github.com/almino/semantic-ui-vue2/blob/master/src/elements/Input.vue , no puede simplemente pasar oyentes directamente para lograr el mismo control. (por ejemplo: tienes que usar v-on: input = "emitInput ($ event.target.value)")

De todos modos, este PR es valioso, ¡buen trabajo!

@AlexandreBonaventure Esto se debe a que v-model funciona de manera ligeramente diferente en los componentes que en los elementos. Los eventos DOM proporcionan un objeto de evento a las devoluciones de llamada, mientras que los eventos de componentes proporcionan un valor directamente. El resultado es que v-model _ sí_ funciona, pero el valor límite es el objeto de evento del DOM. 😕

Creo que tiene razón en que sería deseable que v-model funcionara aquí, pero no estoy seguro de cuál sería el mejor lugar para resolverlo. Algunas ideas:

Tal vez se podría agregar una propiedad no enumerable a $listeners (por ejemplo, __$listeners__: true , para ayudar a v-on detectar usos de v-on="$listeners" . Luego, en los casos en que $listeners objeto se pasa a v-on , cada oyente podría ser envuelto:

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

Una desventaja es que ahora estamos tirando datos. Si alguien quiere acceder a keyCode , por ejemplo, no tiene suerte. Sin embargo, si los modificadores fueran compatibles con la sintaxis del objeto de v-on , podríamos solucionar esto haciendo que .native deshabilite el comportamiento de ajuste automático.

@ yyx990803 @LinusBorg ¿Cuáles son sus pensamientos sobre la viabilidad? ¿Algún caso límite que me falte?

Oh, ya veo, te refieres al modelo v en rral. Formar elementos, estaba pensando en componentes.

No puedes / no deberías usar eso en accesorios de todos modos, con o sin este PR. Y en aplicaciones avanzadas, es bastante poco común usarlo (aunque se puede lograr).

@LinusBorg Solo quiero asegurarme de que estamos en la misma página. Dado un componente CustomInput con esta plantilla:

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

¿No esperaría que el siguiente código funcione?

<CustomInput v-model="myValue" />

Esperaría que funcionara, pero la forma en que entendí a alexandre, se refería al modelo v en el elemento, no al componente, que eventualmente solo funciona con el estado local mutante.

Estaba tratando de decir lo que @chrisvfritz explicó en su última publicación. (El inglés no es mi idioma nativo, lo siento :))

@LinusBorg, el problema de hacer esto en la última versión es que todavía se considera un anti-patrón y activa una advertencia sobre la mutación del estado.

Es extremadamente útil que lo anterior funcione cuando la propiedad de valor es algo diferente a una cadena. Tomemos, por ejemplo, un componente combinado en el que estoy tratando de usar enumeraciones importadas de mi propia biblioteca como valores para las opciones seleccionadas:

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

Este es un ejemplo de una de las listas que uso para elementos en el padre:

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

Y cómo uso el componente combinado:

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

He estado tratando de hacer que esto funcione durante 2 días y todavía me siento realmente frustrado. El problema es que $ event.target.value es siempre una cadena y no se evalúa como debería ser en :value .

@LinusBorg @AlexandreBonaventure @RobertBColton Acabo de abrir un número en el que podemos enfocar la discusión futura de este problema.

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

Temas relacionados

loki0609 picture loki0609  ·  3Comentarios

bdedardel picture bdedardel  ·  3Comentarios

hiendv picture hiendv  ·  3Comentarios

aviggngyv picture aviggngyv  ·  3Comentarios

loki0609 picture loki0609  ·  3Comentarios