Vue: API aprimorada para componentes de IU, reduzindo o padrão para o encaminhamento de atributos e eventos

Criado em 27 jun. 2017  ·  46Comentários  ·  Fonte: vuejs/vue

Qual problema esse recurso resolve?

Existem muitos casos em que os atributos passados ​​para um componente Vue não devem ser adicionados ao elemento raiz, mas sim a um subelemento. Por exemplo, neste componente de IU , uma quantidade incrível de adereços deve ser usada para garantir que os atributos sejam adicionados ao elemento input , em vez do invólucro div .

Além disso, muitas vezes é desejável expor todos os ouvintes de evento em um elemento do formulário para o pai, o que também requer muitos clichês atualmente se o elemento não for a raiz (nesse caso, o modificador .native pode resolver o problema )

Qual é a aparência da API proposta?

EDIT: Comece aqui para pôr em dia a discussão.

Atualmente, por padrão, o elemento "exposto" (aquele ao qual atributos arbitrários podem ser adicionados) é sempre o elemento raiz. Uma nova diretiva pode ser usada para definir um elemento exposto diferente. Algumas idéias para o nome da diretiva:

  • v-expose (provavelmente meu favorito)
  • v-expose-attrs (provavelmente mais claro, mas mais demorado)
  • v-main
  • v-primary

Se v-expose for adicionado a um elemento, ele aceitará atributos passados ​​para seu componente - e esses atributos __não serão mais passados__ para o elemento raiz.

Outros recursos que podem ser bons:

  • Se a diretiva for definida em vários elementos, os atributos serão duplicados em cada um deles
  • No caso em que um subconjunto de atributos deve ser aceito por um elemento, v-expose pode aceitar uma string ou array de strings (por exemplo, v-expose="class" ou v-expose="['class', 'type', 'placeholder']" ). Nesse caso, esses atributos seriam adicionados ao elemento (novamente, em vez de ao elemento raiz), mas todos os outros atributos seriam adicionados ao elemento raiz ou ao (s) elemento (s) com um valor v-expose .
discussion feature request

Comentários muito úteis

@chrisvfritz como isso funcionaria nas funções de renderização?

Acho que seria melhor:

  • fornece uma opção para desativar a herança automática de atributos para o nó raiz
  • expor esses atributos como $attributes , por exemplo (nomeando tbd)
  • use v-bind para adicioná-los onde quiser, como já mostramos para fazer com $props :
v-bind="$attributes"

Isso teria o benefício adicional de trabalhar praticamente idêntico nas funções JSX / render

Todos 46 comentários

Hmm, não sei sobre isso, mas para componentes como esse, acho que você poderia usar JSX ou createElement para espalhar adereços.

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

Para nós, isso seria ótimo. Envolvemos todas as entradas em uma etiqueta para fins de estilo e ux. Concordo que poderíamos descer para jsx, mas os modelos são muito mais fáceis de seguir por todos.

@Austio , infelizmente, essa é a recompensa pelos modelos ... espere ... talvez pudéssemos pensar em uma maneira de spread props em modelos vue?

Eu gosto desse recurso pessoalmente. Mas parece quebrar a consistência do comportamento v-bind, como às vezes eu ainda preciso vincular a propriedade class para o elemento raiz.

Então, que tal usar um par de diretivas como getter e setter como:

Dentro do componente, defina uma âncora v-expose :

<input v-expose="foo" />

Ao usá-lo:

<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 , isso parece incrível, mas, novamente, parece um spread básico e com problemas em potencial como você definiria @keyup.enter.prevent="myAction" ?

você não pode simplesmente <the-component :foo="{'@keyup.enter.prevent': myAction}"></the-component> , isso significa que você terá que manter todos os modificadores como enter e prevent no tempo de execução (que é uma parte do vue-template-compiler atm)

@nickmessing

que parece uma propagação básica

O que estamos falando é trazer algo como spread para usuários de template

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

@ é um v-on shortland, não significa prop (v-bind).

@jkzing , no link da descrição há várias ligações v-on também

@nickmessing Hum ... Quanto às ligações v-on , é outro tópico da IMO, como bolha de eventos. 🤔

@jkzing , esse era todo o conceito de v-expose afaik, fazer todas as propriedades "irem" para um determinado elemento no componente

@nickmessing , Não tenho certeza sobre a proposta original, mas não acho que um ouvinte de evento deva ser considerado como attribute .

@jkzing , provavelmente não, mas considerando o exemplo comum de <my-awesome-text-input /> onde você pode ter> 9.000 adereços diferentes, você só quer que todos eles cheguem a você <input /> que está dentro de seu componente personalizado sem uma tonelada de código.

Eu pessoalmente uso v-bind="$props" ou você pode filtrá-los para excluir os adereços que não deseja aplicar. Dessa forma, você pode aplicar vários adereços de uma vez em uma entrada. Na verdade, v-expose pode ser útil porque, para componentes de invólucro, como entradas, você deve especificar todos os adereços html

Então, é isso
https://github.com/almino/semantic-ui-vue2/blob/master/src/elements/Input.vue#L9
cana-de-açúcar pode ser reduzida a v-bind="$props" ou v-bind="filteredProps" onde filterProps pode ser alguma propriedade computada

@cristijora Estamos usando v-bind="someProps" também. O problema com essa solução é que propriedades excessivas serão adicionadas como atributos HTML. Seria ótimo se v-bind= pudesse filtrar todas as propriedades que não são aceitas pelo componente. Com <component> dinâmico, não sabemos quais adereços filtrar na propriedade computada. Embora seja possível extrair options.props e usar lodash._pick .

Isso é realmente viável com uma diretiva?

@posva , não acho que funcione como uma diretiva em si, mas pode ser uma parte do motor de template vue que faz algo como se espalhar internamente + alguma propagação de evento

@posva Não é uma diretiva construída pelo usuário, não acho, então posso estar usando a linguagem errada. O que quero dizer é apenas um "atributo especial".

@chrisvfritz , você tem alguma opinião sobre uma API e como ela seria usada (especificando o que expor e como adicionar à criança)

Eu poderia ver isso sendo semelhante no uso para fornecer / injetar conceito.

@Austio Posso não estar entendendo a pergunta, mas apresento algumas idéias sobre a API no post original.

Ei, Chris, significou pensamentos adicionais sobre o uso de similar para fornecer injeção, onde você declara o que deve ser exposto no pai e, em seguida, usa isso na criança.

Ah, entendo. Não tenho certeza se há necessidade disso. As informações já podem ser passadas por adereços e slots - e até mesmo propriedades privadas no pai podem ser acessadas com this.$parent , embora eu ache que é melhor evitar esse padrão.

@Austio Você está pensando em um caso de uso específico?

@chrisvfritz como isso funcionaria nas funções de renderização?

Acho que seria melhor:

  • fornece uma opção para desativar a herança automática de atributos para o nó raiz
  • expor esses atributos como $attributes , por exemplo (nomeando tbd)
  • use v-bind para adicioná-los onde quiser, como já mostramos para fazer com $props :
v-bind="$attributes"

Isso teria o benefício adicional de trabalhar praticamente idêntico nas funções JSX / render

@LinusBorg Gosto da sua maneira de pensar. 😄 Seu jeito é muito mais intuitivo.

Como nota lateral, acho que com esta API em vigor, a próxima versão principal do Vue poderia até mesmo remover a herança automática de atributos por completo, para que a comunicação entre componentes pudesse permanecer explícita em ambos os lados.

Seria possível depreciar ou remover esse comportamento, sim.

Se isso valer a pena, as mudanças possivelmente necessárias em muitos componentes das bibliotecas, etc., devem ser decididas e devem ser discutidas com a comunidade, especialmente os autores da coleção de UI.

A sobre o recurso questionado: esta informação já está disponível nos componentes funcionais por meio de context.data.attributes , portanto, esse recurso forneceria basicamente a funcionalidade idêntica aos componentes da instância.

Sim, exatamente. O objetivo principal que tenho em mente é tornar o trabalho mais simples para autores de componentes de IU (internos e externos). Atualmente, há muitos casos em que algo assim é necessário:

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

Uma nova propriedade $attributes poderia encurtá-lo para este:

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

Embora então eu suponha que ainda seria bom ter alguma maneira de também expor os eventos. Talvez uma diretiva v-on vazia pudesse encaminhar todos os ouvintes de evento no pai para este elemento?

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

Ou se houver várias preocupações que queremos agrupar, podemos estar de volta a algo como v-expose :

<input v-expose>

Isso se transformou em uma discussão mais ampla sobre como simplificar a construção de componentes de interface do usuário, em vez de uma solicitação de recurso específica, portanto, renomearei esse problema. 🙂

Estou atrasado para este tópico, mas também tenho algumas ideias.

v-bind Solução atual e desvantagens

Em primeiro lugar, já uso e adoro o recurso v-bind="propObject" (tão poderoso). Por exemplo, bootstrap-vue tem um componente de link interno que é usado em qualquer lugar (botões, navs, listas suspensas, etc.). O componente gira se tornando uma âncora nativa vs. um link de roteador baseado em href vs. to e presença de vm.$router , então há muitas propriedades para passar condicionalmente para cada um desses componentes.

Nossa solução foi colocar esses adereços em um mixin e usar v-bind="linkProps" com um objeto computado. Isso funciona muito bem, mas ainda é uma grande sobrecarga adicionar esse mixin a _todos os outros componentes usando o componente de link_.

v-expose Possibilidades usando v-bind

Eu pessoalmente gosto do conceito de v-expose , e talvez funcione como slot padrão + slots nomeados, e então use modificadores para acessar os slots de atributos nomeados.

O atributo padrão _ "slot" _ sempre passaria atributos para o próprio componente (sem alteração), enquanto outros destinos nomeados poderiam ser especificados pelo componente. Talvez algo assim:

<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

Não tenho muito a acrescentar aqui, exceto que .native é um modificador incrivelmente poderoso. Resolveu muitos problemas para mim. No entanto, parece amplamente desconhecido para os devs do Vue (vejo uma grande quantidade de problemas de lib da IU que são resolvidos ao expor os devs a esse recurso). Coloquei um PR no site para adicionar mais documentos e suporte de pesquisa no site e potencialmente otimizado para pesquisa do Google.

Vindo de uma discussão sobre a superfície da API em outra edição, devo repetir que não sou fã da ideia v-expose . ele apresenta outra "maneira como as coisas funcionam", e não funciona para JSX sem também implementar algo especial lá, etc.

Uma coisa que respeito no pessoal do React é seu compromisso com uma API simples e usando os recursos da linguagem o máximo possível. Nesse espírito, reutilizar um padrão que já temos para adereços para atributos parece muito melhor do que introduzir outra abstração.

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

Ahh, entendo o que você está dizendo agora. E eu gosto! Isso está disponível atualmente? vm.$attributes seria a adição em vez disso?

Relendo seus comentários. Estou rastreando agora 👍

Sim, $attributes seria a adição.

Além disso, precisaríamos de uma opção para desativar o comportamento padrão atual de aplicação de atributos ao elemento raiz, como este:
`` `html

Isso poderia se tornar uma configuração padrão no Vue 3.0 se decidirmos fazer isso resultando em uma alteração significativa.

@LinusBorg Qual é sua opinião sobre como lidar com o lado dos eventos? Para seguir a mesma estratégia, suponho que também poderíamos adicionar uma propriedade $listeners , que pode se parecer com isto:

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

Então, talvez v-on pudesse aceitar um objeto, semelhante a v-bind . Portanto, teríamos:

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

Um problema que prevejo é com input / change eventos, uma vez que v-model funciona de forma ligeiramente diferente para componentes do que para elementos. Também não sei se desejaríamos $listeners e $nativeListeners . Suponho que se $listeners estivesse disponível, então o modificador .native pode estar obsoleto.

Além disso, em relação à opção applyComponentAttrsToRoot , talvez exposeRootEl seja um bom nome, que quando definido como false , pode desativar tanto os atributos aplicados automaticamente quanto o evento .native encaminhamento?

Também pode ser bom ser capaz de desabilitar isso para o aplicativo inteiro por meio de Vue.config , bem como para um único componente.

Recentemente, tive uma ideia semelhante sobre $listeners - também está disponível em componentes funcionais via

context.data.listeners

Assim, acabaríamos com $props , $attributes , $listeners que parece bom para mim.

Há também # 5578 solicitando a sintaxe do objeto v-on="{...}" como eu usei para $attributes , ela se encaixaria perfeitamente.

Mas não tenho certeza sobre o modificador .native. Para fazer isso funcionar com eventos de componente e ouvintes nativos, a API ficaria muito mais complicada e o uso é questionável, já que um ouvinte de evento nativo aplicado ao elemento raiz ainda pegaria o evento desejado borbulhando, então pode não será necessário atribuí-lo a um elemento específico no modelo.

Em geral, eu diria que para bibliotecas de componentes de baixo nível, as funções de renderização devem ser preferidas quando os modelos estão ficando difíceis de trabalhar. Mas concordo que o seguinte seria valioso:

  1. Desativando o comportamento padrão de "aplicação automática de vinculações que não são encontradas em props como atributos para o elemento raiz" (problema relacionado: isso deve afetar class e style vinculações também?)

  2. expondo uma maneira mais fácil de "herdar" ligações externas no componente para um elemento interno que não é necessariamente a raiz. Idealmente, com consistência entre modelos e funções de renderização.

ia kie like vue, ferramentas simples

Só quero dizer que o PR para isso na v2.4 é excelente! 👍

Da nota de lançamento

Combinar isso nos permite simplificar um componente como este para o seguinte:

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

Parece bom, mas não é bem verdade, já que esses tipos de componentes são projetados para funcionar com o modelo v e, até onde eu sei, o modelo v no componente de embalagem não está funcionando fora da caixa. Existe algum exemplo de como encaminhar o modelo v de um componente de embalagem para uma entrada, por exemplo?
A maneira mais simples que encontrei:
https://jsfiddle.net/60xdxh0h/2/

Talvez com o componente funcional trabalhando junto com o modelo, seria mais simples

esses tipos de componentes são projetados para funcionar com o modelo v e, pelo que eu sei, o modelo v no componente de embalagem não está funcionando fora da caixa.

Porque você pensaria isso? v-model é apenas um açúcar de sintaxe para um prop e um ouvinte de evento, ambos estarão em $ attr / $ props e, portanto, podem ser facilmente repassados.

Suponho que a única coisa que exigiria conhecimento das opções do filho seria se o filho alterasse os model padrões, isso é verdade.

Mas seria possível lê-los, dependendo das circunstâncias.

Claro que é um açúcar sintático, mas quero dizer que pode ser confuso de ler

Combinar isso nos permite simplificar um componente como este para o seguinte:

quando realmente baseado no exemplo https://github.com/almino/semantic-ui-vue2/blob/master/src/elements/Input.vue , você não pode simplesmente passar ouvintes diretamente, para obter o mesmo controle. (por exemplo: você deve usar v-on: input = "emitInput ($ event.target.value)")

De qualquer forma, esse PR é valioso, bom trabalho!

@AlexandreBonaventure Isso ocorre porque v-model funciona de maneira um pouco diferente nos componentes do que nos elementos. Os eventos DOM fornecem um objeto de evento para retornos de chamada, enquanto os eventos de componente fornecem um valor diretamente. O resultado é que v-model _faz_ funciona, mas o valor vinculado é o objeto de evento do DOM. 😕

Acho que você está certo ao dizer que seria desejável que v-model trabalhasse aqui, mas não tenho certeza de onde seria o melhor lugar para resolver isso. Algumas ideias:

Talvez uma propriedade não enumerável pudesse ser adicionada a $listeners (por exemplo, __$listeners__: true , para ajudar v-on detectar os usos de v-on="$listeners" . Então, nos casos em que $listeners objeto é passado para v-on , cada ouvinte pode ser encapsulado:

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

Uma desvantagem é que agora estamos jogando fora dados. Se alguém deseja acessar um keyCode , por exemplo, está sem sorte. No entanto, se os modificadores fossem suportados para a sintaxe de objeto de v-on , poderíamos corrigir isso fazendo com que .native desabilitasse o comportamento de empacotamento automático.

@ yyx990803 @LinusBorg Qual é sua opinião sobre a viabilidade? Algum caso extremo que estou perdendo?

Oh, entendo, você está se referindo ao modelo v em rral. Elementos de forma, eu estava pensando sobre isso em componentes.

Você não pode / não deve usar isso em adereços de qualquer maneira, com ou sem este PR. E em aplicativos avançados, é bastante incomum usá-lo (embora seja possível).

@LinusBorg Só quero ter certeza de que estamos na mesma página. Dado um componente CustomInput com este modelo:

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

Você não esperaria que o código abaixo funcionasse?

<CustomInput v-model="myValue" />

Eu esperava que funcionasse - mas da forma como entendi alexandre, ele estava se referindo ao modelo v no elemento, não no componente - que, eventualmente, só funciona com estado local mutante.

Eu estava tentando dizer o que @chrisvfritz explicou em seu último post. (Desculpe, inglês não é minha língua nativa :))

@LinusBorg o problema de fazer isso na versão mais recente é que ainda é considerado um

É extremamente útil ter o acima funcionando onde a propriedade value é algo diferente de uma string. Tome, por exemplo, um componente de combinação em que estou tentando usar enums importados de minha própria biblioteca como valores para as opções selecionadas:

<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 é um exemplo de uma das listas que uso para itens no pai:

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

E como uso o componente combo:

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

Estou tentando fazer isso funcionar há 2 dias e ainda estou muito frustrado. O problema é que $ event.target.value é sempre uma string e não é avaliada como deveria ser em :value .

@LinusBorg @AlexandreBonaventure @RobertBColton Acabei de abrir uma edição em que podemos enfocar uma discussão futura desse problema.

Esta página foi útil?
0 / 5 - 0 avaliações