Vue: [Abandonado] RFC: Simplifique o uso de slots com escopo

Criado em 11 dez. 2018  ·  36Comentários  ·  Fonte: vuejs/vue

Este é um acompanhamento de https://github.com/vuejs/vue/issues/7740#issuecomment -371309357

Racional

Problemas com o uso atual do slot com escopo:

  • Detalhado se estiver usando <template slot-scope>
  • Limitado a um elemento / componente está usando slot-scope diretamente no elemento de slot.

Proposta

Apresente uma nova diretiva v-scope , que só pode ser usada em componentes:

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

Funcionaria da mesma forma que slot-scope para o slot com escopo padrão para <comp> (com <comp> fornecendo o valor do escopo). Portanto, também funciona com a desconstrução:

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

Por que uma nova diretriz

Acredito que a equipe discutiu brevemente o que propus em https://github.com/vuejs/vue/issues/7740#issuecomment -371309357 no Slack há algum tempo, mas não consegui mais encontrar o registro do bate-papo. Aqui está o raciocínio por trás de uma nova diretiva:

  • slot-scope foi introduzido como um atributo especial em vez de uma diretiva (atributos que começam com v- prefixo) porque slot é um atributo, e queríamos manter os atributos relacionados ao slot consistentes . slot foi, por sua vez, introduzido como um atributo não diretivo porque queremos que o uso espelhe o uso real do slot no padrão Shadow DOM. Achamos que seria melhor evitar ter nossos próprios v-slot paralelos quando há algo que é conceitualmente o mesmo no padrão.

  • Originalmente, slot-scope foi projetado para ser usado apenas em elementos <template> que atuam como recipientes abstratos. Mas isso era prolixo - então introduzimos a capacidade de usá-lo diretamente em um elemento de slot sem envolver <template> . No entanto, isso também torna impossível permitir o uso de slot-scope diretamente no próprio componente, porque isso levaria à ambigüidade, conforme ilustrado aqui .

  • Pensei em adicionar modificadores ou prefixos especiais a slot-scope para que possamos usá-lo em um componente diretamente para indicar que seu conteúdo de slot deve ser tratado como o slot de escopo padrão, mas nem um modificador ou um prefixo como $ parece ser o ajuste certo. O modificador por design só deve ser aplicado a diretivas, enquanto a nova sintaxe especial para um único caso de uso é inconsistente com todo o design de sintaxe.

  • Por muito tempo, evitamos adicionar mais diretivas, parte disso sendo a sintaxe do modelo é algo que queremos manter o mais estável possível, parte disso é que queremos manter as diretivas básicas no mínimo e apenas fazer coisas que os usuários não podem fazer facilmente no ambiente do usuário. No entanto, neste caso, o uso de slots com escopo definido é importante o suficiente, e acho que uma nova diretiva pode ser justificada por tornar seu uso significativamente menos ruidoso.

Preocupações

  • A expressão aceita por v-scope é diferente da maioria das outras diretivas: ela espera um nome de variável temporário (que também pode ser uma desconstrução), mas não sem precedência: atua exatamente como a parte de alias de v-for . Portanto, conceitualmente v-scope cai no mesmo campo com v-for como uma diretiva estrutural que cria variáveis ​​temporárias para seu escopo interno.

  • Isso quebraria o código do usuário se o usuário tiver uma diretiva personalizada chamada v-scope e for usada em um componente.

    • Como as diretivas personalizadas na v2 são focadas principalmente em manipulações diretas de DOM, é relativamente raro ver diretivas personalizadas usadas em componentes, ainda mais para algo que passa a ser chamado de v-scope , então o impacto deve ser mínimo.

    • Mesmo no caso de realmente acontecer, é simples de lidar simplesmente renomeando a diretiva personalizada.

discussion intend to implement

Comentários muito úteis

Resolvendo o problema

Tentando sintetizar, parece que queremos uma solução que irá:

  • reduzir o padrão necessário para acessar dados de slots com escopo definido
  • evite sugerir que os dados para o slot padrão estão disponíveis em slots nomeados
  • minimizar o número de novas APIs que temos que apresentar
  • não reinvente APIs onde a especificação de componentes da web já tem uma solução aceitável
  • seja explícito, para evitar confusão sobre a origem dos dados

Posso ter uma solução que trate de tudo isso! 🤞 Com $event para v-on , temos um precedente de fornecer expressões executadas dentro de uma função implícita com um argumento nomeado. As pessoas parecem gostar dessa conveniência e não vejo muita confusão causada por ela, então talvez devêssemos seguir esse exemplo para slots com escopo definido.

Solução proposta

Em vez de usar v-scope , poderíamos disponibilizar o escopo do slot como $slot . Por exemplo:

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

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

Para contexto, o modelo filho pode ser semelhante a:

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

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

Quando os slots são aninhados, o objeto $slot mesclaria os dados dos slots, com os slots mais internos tendo prioridade de substituição. Por exemplo, em:

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

a fusão pode ter a seguinte aparência:

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

Você pode estar se preocupando / imaginando como lidar com a sobreposição de namespace e, em 99,9% dos casos, realmente não acho que isso seja um problema. Por exemplo:

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

No exemplo acima, $slot.user sempre terá o valor correto, apesar dos escopos de aninhamento. No entanto, às vezes você realmente precisa acessar as duas propriedades ao mesmo tempo, como em:

<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 esses casos raros, os usuários ainda podem usar slot-scope com seu comportamento atual como uma saída de emergência . Mas v-scope ainda seria desnecessário, pois se bem entendi, seu propósito seria simplificar os casos de uso mais comuns, que o objeto $slot já teria realizado sem causar os mesmos problemas.

Vantagens

  • Quando um usuário deseja acessar dados fornecidos por um slot, o único padrão é $slot , que é tão breve quanto pode ser, embora permaneça explícito. É imediatamente óbvio que os dados vieram de um slot, sem a necessidade de pesquisar no componente para ver onde uma propriedade está definida.

  • Para saber quais dados podem acessar, os usuários só precisam pensar em quais slots o conteúdo será renderizado . Acho que com v-scope , haveria muita confusão com as pessoas assumindo que funciona como v-for , porque essa é a única outra diretiva que define propriedades de contexto com escopo definido.

  • Os usuários não precisam estar familiarizados e confortáveis ​​com a desestruturação para usar slots com escopo definido , reduzindo assim a curva de aprendizado.

  • Em alguns casos, especialmente com muitos slots aninhados que fornecem estado, não será óbvio _de qual_ componente algum estado veio e os usuários terão que alcançar slot-scope novamente. Isso pode parecer uma desvantagem, mas quando vejo slots com escopo aninhado, quase sempre é para o padrão de provedor de estado, que acredito fortemente ser um antipadrão. Aqui está meu raciocínio . Portanto, o fato de que muitos slots de escopo aninhados exigiriam mais clichês poderia, na verdade, diminuir o uso de antipadrões .

  • A área de superfície da API é drasticamente reduzida , porque a maioria dos desenvolvedores do Vue só terá que lembrar $slot , que é apenas um objeto.

  • Ao usar uma propriedade com o prefixo $ , estamos construindo a API de slot de componente da web de uma forma que deixa claro que $slot é algo Vue.

  • Atualmente, as bibliotecas costumam fazer algo como <slot v-bind="user" /> para que seus usuários possam salvar alguns caracteres com slot-scope="user" vez de slot-scope="{ user } . Parece elegante à primeira vista, mas passei a experimentar como um antipadrão. O problema surge quando o componente deseja expor dados _outro_que_ do usuário. Então, eles têm duas opções: fazer uma alteração significativa em sua API ou forçar essa nova propriedade no objeto user , mesmo que tenha muito pouco a ver com o usuário. Nenhuma delas é uma ótima opção. Felizmente, $slot eliminaria a tentação de tornar os componentes menos à prova de futuro , porque, embora $slot ainda seja mais curto do que $slot.user , você perde um contexto importante ao definir o usuário como $slot .

Desvantagens

  • Em alguns slots de escopo aninhados, há alguns casos extremos em que não será óbvio _de qual componente vieram alguns dados. Nesses casos, porém, os usuários ainda podem alcançar slot-scope quando precisam de explicitação máxima, então não acho que seja um grande problema. Além disso, como mencionei anteriormente, acho que é muito provável que o usuário esteja atirando no próprio pé com componentes de provedor estatal se ele tiver esse problema em primeiro lugar.

Todos 36 comentários

Parece bom. Eu estava pensando em usar o argumento para fornecer um nome de slot, mas isso permitiria que várias diretivas v escopo fossem usadas no mesmo componente e, portanto, reutilizar o conteúdo fornecido para o componente. Que não tenho certeza se é útil ou se pode levar a problemas

Overral, isso vai melhorar o uso da API de componentes sem renderização, que estão sendo mais usados ​​com o tempo 🙌

Se bem entendi, v-scope serve apenas para um caso de uso. Em vez de escrever:

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

Nós podemos escrever:

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

Reduziu bastante o ruído neste caso, mas parece ser o único caso. Parece um exagero introduzir uma nova diretiva (que funciona de maneira bem diferente de outras diretivas, até mesmo de v-for porque funciona apenas em componentes e depende da lógica <slot> por baixo). Outra preocupação é que quando procuro v-scope neste repo , as duas únicas ocorrências estão discutindo sobre a redução do escopo de dados de uma subárvore de modelo (https://github.com/vuejs/vue/issues/5269 # issuecomment-288912328, https://github.com/vuejs/vue/issues/6913), por exemplo, como with funciona em JavaScript.

Eu gosto disso. Uma dúvida que resta para mim é como queremos lidar com os slots nomeados. Se quisermos permitir que slots nomeados usem a mesma diretiva, precisaríamos permitir isso em <template> também:

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

Poderíamos usar esta oportunidade para descontinuar sot-scope e removê-lo no Vue 3, substituindo-o por v-scope

Acho que definitivamente não devemos terminar com dois conceitos diferentes ( v-scope diretiva e slot-scope attibute) para a mesma coisa.

Nota lateral: Agora, olhando para isso, as pessoas podem ter a impressão da hierarquia de elementos e
diretivas, que eles podem acessar otherScope e scope no slot nomeado. Pode ser uma desvantagem.

@LinusBorg para o nome, um argumento poderia v-scope:slotName . Acho que o objetivo de permitir isso apenas em componentes é substituir o que @Justineo disse (https://github.com/vuejs/vue/issues/9180#issuecomment-446168296)

Nota lateral: Agora, olhando para isso, as pessoas podem ter a impressão da hierarquia de elementos e
diretivas, que eles podem acessar otherScope e escopo no slot nomeado. Pode ser uma desvantagem.

Isso funciona, certo? 🤔 Tenho quase certeza de que aninhei escopos de slot

Isso funciona, certo? 🤔 Tenho quase certeza de que aninhei escopos de slot

Eu não os aninhei, porém, eles são slots de irmãos. Eu defini o escopo do slot padrão com v-scope no componente, e o slot nomeado (que é um slot irmão ) com <template slot="someName">
Ver? É confuso ^^

@LinusBorg Acho que o uso seria confuso. Acho que conceitualmente é:

  • Quando v-scope é usado diretamente no componente, significa que você está usando apenas o slot padrão.
  • Se você quiser usar slots nomeados ... você ainda precisa usar slot + slot-scope .

Concordo que ter v-scope e slot-scope pode ser inconsistente / confuso, especialmente para novos usuários que não sabem como chegamos ao design atual.

Resolvendo o problema

Tentando sintetizar, parece que queremos uma solução que irá:

  • reduzir o padrão necessário para acessar dados de slots com escopo definido
  • evite sugerir que os dados para o slot padrão estão disponíveis em slots nomeados
  • minimizar o número de novas APIs que temos que apresentar
  • não reinvente APIs onde a especificação de componentes da web já tem uma solução aceitável
  • seja explícito, para evitar confusão sobre a origem dos dados

Posso ter uma solução que trate de tudo isso! 🤞 Com $event para v-on , temos um precedente de fornecer expressões executadas dentro de uma função implícita com um argumento nomeado. As pessoas parecem gostar dessa conveniência e não vejo muita confusão causada por ela, então talvez devêssemos seguir esse exemplo para slots com escopo definido.

Solução proposta

Em vez de usar v-scope , poderíamos disponibilizar o escopo do slot como $slot . Por exemplo:

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

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

Para contexto, o modelo filho pode ser semelhante a:

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

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

Quando os slots são aninhados, o objeto $slot mesclaria os dados dos slots, com os slots mais internos tendo prioridade de substituição. Por exemplo, em:

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

a fusão pode ter a seguinte aparência:

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

Você pode estar se preocupando / imaginando como lidar com a sobreposição de namespace e, em 99,9% dos casos, realmente não acho que isso seja um problema. Por exemplo:

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

No exemplo acima, $slot.user sempre terá o valor correto, apesar dos escopos de aninhamento. No entanto, às vezes você realmente precisa acessar as duas propriedades ao mesmo tempo, como em:

<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 esses casos raros, os usuários ainda podem usar slot-scope com seu comportamento atual como uma saída de emergência . Mas v-scope ainda seria desnecessário, pois se bem entendi, seu propósito seria simplificar os casos de uso mais comuns, que o objeto $slot já teria realizado sem causar os mesmos problemas.

Vantagens

  • Quando um usuário deseja acessar dados fornecidos por um slot, o único padrão é $slot , que é tão breve quanto pode ser, embora permaneça explícito. É imediatamente óbvio que os dados vieram de um slot, sem a necessidade de pesquisar no componente para ver onde uma propriedade está definida.

  • Para saber quais dados podem acessar, os usuários só precisam pensar em quais slots o conteúdo será renderizado . Acho que com v-scope , haveria muita confusão com as pessoas assumindo que funciona como v-for , porque essa é a única outra diretiva que define propriedades de contexto com escopo definido.

  • Os usuários não precisam estar familiarizados e confortáveis ​​com a desestruturação para usar slots com escopo definido , reduzindo assim a curva de aprendizado.

  • Em alguns casos, especialmente com muitos slots aninhados que fornecem estado, não será óbvio _de qual_ componente algum estado veio e os usuários terão que alcançar slot-scope novamente. Isso pode parecer uma desvantagem, mas quando vejo slots com escopo aninhado, quase sempre é para o padrão de provedor de estado, que acredito fortemente ser um antipadrão. Aqui está meu raciocínio . Portanto, o fato de que muitos slots de escopo aninhados exigiriam mais clichês poderia, na verdade, diminuir o uso de antipadrões .

  • A área de superfície da API é drasticamente reduzida , porque a maioria dos desenvolvedores do Vue só terá que lembrar $slot , que é apenas um objeto.

  • Ao usar uma propriedade com o prefixo $ , estamos construindo a API de slot de componente da web de uma forma que deixa claro que $slot é algo Vue.

  • Atualmente, as bibliotecas costumam fazer algo como <slot v-bind="user" /> para que seus usuários possam salvar alguns caracteres com slot-scope="user" vez de slot-scope="{ user } . Parece elegante à primeira vista, mas passei a experimentar como um antipadrão. O problema surge quando o componente deseja expor dados _outro_que_ do usuário. Então, eles têm duas opções: fazer uma alteração significativa em sua API ou forçar essa nova propriedade no objeto user , mesmo que tenha muito pouco a ver com o usuário. Nenhuma delas é uma ótima opção. Felizmente, $slot eliminaria a tentação de tornar os componentes menos à prova de futuro , porque, embora $slot ainda seja mais curto do que $slot.user , você perde um contexto importante ao definir o usuário como $slot .

Desvantagens

  • Em alguns slots de escopo aninhados, há alguns casos extremos em que não será óbvio _de qual componente vieram alguns dados. Nesses casos, porém, os usuários ainda podem alcançar slot-scope quando precisam de explicitação máxima, então não acho que seja um grande problema. Além disso, como mencionei anteriormente, acho que é muito provável que o usuário esteja atirando no próprio pé com componentes de provedor estatal se ele tiver esse problema em primeiro lugar.

Acho que uma propriedade com o prefixo $ deve ser consistente dentro de uma instância inteira do Vue. Não tenho certeza se isso causará confusão ao injetar implicitamente $slot em cada slot e eles representam um argumento local dentro desses escopos de slot. É uma espécie de convenção (de acordo com minha observação) que $ -prefixed propriedades representam algo disponível para cada instância Vue, então eles podem ser usados ​​dentro de qualquer lugar, incluindo ganchos de ciclo de vida, métodos e funções de renderização. Adicionar este $slot quebrará esta convenção.

@chrisvfritz esta é uma proposta interessante, mas meio que depende de slots normais e slots com escopo sendo unificados (então talvez algo que possa ser considerado na v3). O maior problema com isso é se o conteúdo do slot deve estar disponível no componente filho $slots ou $scopedSlots ? A única dica é a presença de $slot em algum lugar nas expressões (pode estar em qualquer lugar na árvore), que não é tão explícito quanto slot-scope (que só pode estar na raiz do slot). Embora tecnicamente possamos detectar isso no compilador, para o usuário, o slot se moverá de $slots para $scopedSlots sempre que o usuário começar a usar $slot no modelo ...

Isso terá algum grande impacto sobre os usuários JSX?

@donnysim não, isso não afeta JSX de forma alguma.

Acho que uma propriedade com o prefixo $ deve ser consistente dentro de uma instância inteira do Vue. Não tenho certeza se isso causará confusão ao injetar implicitamente $slot em cada slot e eles representam um argumento local dentro desses escopos de slot.

@Justineo Eu pensei a mesma coisa no início, mas fazemos isso por $event e ninguém parece reclamar, então já existe um precedente e temos evidências de que os usuários normalmente não ficam confusos e realmente gostam da conveniência.

O maior problema com isso é o conteúdo do slot estar disponível no componente filho $slots ou $scopedSlots ?

@ yyx990803 Ótima pergunta! Tenho uma ideia que pode funcionar. E se nos casos em que for ambíguo (slots aninhados usando $slot ), nós apenas compilássemos todos os slots para um slot com escopo definido, mas também tornássemos todos os $scopedSlots disponíveis em $slots como getters? Por exemplo, 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
  })
}

Dessa forma, no caso de:

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

Podemos compilar para 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))]
                },
              },
            ]),
          }),
        ]
      },
    },
  ]),
}) 

E não importa se foo vem de <A> ou <B> , porque dentro desses componentes tanto this.$slots.default quanto this.$scopedSlots.default(someData) funcionarão.

Algumas advertências:

  • Em casos com slots aninhados e apenas alguns deles têm realmente escopo, as funções de renderização compiladas de modelos usando $slot seriam ligeiramente maiores do que se usando slot-scope . Não muito significativamente, então acho que está tudo bem.

  • Não consigo pensar em um exemplo real, mas pode haver casos extremos em que um usuário está iterando mais de $slots / $scopedSlots e ter propriedades novas ou duplicadas pode resultar em comportamento inesperado. Eu iterou dinamicamente os nomes $slots e $scopedSlots antes para habilitar alguns padrões interessantes, mas essa mudança não afetaria nenhum dos casos de uso que encontrei.

Pensamentos?

fazemos isso por $event e ninguém parece reclamar

@chrisvfritz Oh, eu perdi a coisa de $event . Embora seja claramente uma exceção à convenção (eu costumo acreditar que seja 😅), $event só está disponível em um escopo muito limitado (apenas dentro dos literais de atributo v-bind e sem aninhamento).

E para fazer proxy $scopedSlots em $slots :

Eu costumava acreditar que os slots e os slots com escopo são conceitos diferentes e o Vue tem um uso separado para eles, o que significa que posso ter um slot e um slot com escopo compartilhando o mesmo nome, mas servir a propósitos diferentes. Mais tarde, porém, descobri que o Vue, na verdade, faz fallback para o slot com o mesmo nome quando o slot com escopo especificado não está disponível. Para mim, isso significa que devemos considerá-los a mesma coisa, mas como temos $slots e $scopedSlots disponíveis em cada instância, podemos sempre usá-los em funções de renderização de uma forma mais flexível / inesperada , como usar um slot padrão para substituir todos os itens da lista e um slot com escopo padrão para substituir um único item. Na verdade, isso não é recomendado (não sugerimos o uso recomendado em nossos documentos e no guia de estilo AFAIK), pois é provável que os mesclemos em um único conceito de slot no 3.0, mas fazer isso no 2.x algum uso permitido há muito tempo.

é provável que os mesclemos em um único conceito de slot no 3.0, mas fazer isso no 2.x interromperá o uso permitido há muito tempo.

@Justineo Posso estar entendendo mal, mas não estou sugerindo já mesclá-los no 2.x - apenas estendendo o comportamento de fallback atual. O caso de uso que você descreveu, em que $slots e $scopedSlots usam o mesmo namespace, ainda seria possível porque o comportamento de slot-scope permaneceria inalterado. Por exemplo:

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

ainda funcionaria exatamente da mesma. Isso faz sentido?

@chrisvfritz

Eu não estava falando sobre mesclá-los no 2.x. Eu estava dizendo se você fizer isso:

tornar todos os $scopedSlots disponíveis em $slots como getters

Você não pode mais distinguir $slots.foo de $scopedSlots.foo e usá-los separadamente em funções de renderização.

Você não pode mais distinguir $ slots.foo de $ scopedSlots.foo e usá-los separadamente em funções de renderização.

Honestamente, eu ficaria feliz se tudo (slots e scopedSlots) pudesse ser acessado em scopedSlots como funções, isso significa que não há mais verificações inúteis de qual slot renderizar. Realmente não faz diferença da perspectiva dos componentes que tipo de slot o desenvolvedor usa. Vindo de um JSX misto e usuário de sintaxe de modelo.

@donnysim Sim, concordo com você que eles devem ser mesclados. Expliquei por que tenho essa preocupação em https://github.com/vuejs/vue/issues/9180#issuecomment -447185512. É uma questão de compatibilidade com versões anteriores.

Você não pode mais distinguir $slots.foo de $scopedSlots.foo e usá-los separadamente em funções de renderização.

@Justineo Você _pode_ na verdade, contanto que processemos $slots primeiro e não substituamos slots já definidos. Veja o exemplo de implementação que postei acima:

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 o seguinte exemplo:

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

Se o usuário do componente só passou vm.$scopedSlots.default , ele deve fazer um loop por this.items e renderizar item s no slot com escopo definido. Mas agora que vm.$scopedSlot.default é o slot com escopo e existe, o slot com escopo será chamado em vez do slot.

@Justineo Acho que este caso de uso seria muito raro, mas o padrão ainda seria possível mudando:

if (!this.$slots.default) {

para:

if (this.$scopedSlots.default) {

ou, se o usuário às vezes fornecerá um slot com escopo que deve ser ignorado por algum motivo:

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

Eu ainda não consideraria isso uma mudança significativa. Em primeiro lugar, nunca documentamos / recomendamos o uso de slots e slots com escopo definido com o mesmo nome, portanto, isso nunca fez parte do contrato público. Em segundo lugar, como você mencionou, os slots com escopo definido em modelos já recorrem a um slot sem escopo com o mesmo nome, o que serve como uma boa evidência histórica de que nunca pretendemos que o limite com escopo / sem escopo fosse usado dessa forma.

Isso faz sentido? Além disso, você viu algum caso de uso real para reutilizar nomes de slot? Ou você pode pensar em algum? Não posso, mas se ele permitir _existem_ padrões úteis e únicos, seria bom aprender sobre eles agora, porque também influenciaria nossa decisão de mesclá-los no Vue 3.

Acho que slots com escopo e slots com o mesmo nome para finalidades diferentes são
Uma má ideia. Eu usei isso na versão pré do Vue prometido e removi
porque era confuso. E agora eu tenho que verificar o slot padrão
e slot com escopo para que o dev possa fornecer um slot sem consumir os dados.
E se bem me lembro, você não pode ter o mesmo nome ao usar
modelos.

@chrisvfritz

Eu não estava dizendo que o padrão é útil e deve ser apoiado. De fato, pode causar confusão. Eu estava dizendo que é possível quebrar o código existente. Nunca documentamos o compartilhamento do mesmo nome entre slots e slots com escopo definido, mas nunca sugerimos que os usuários também não o façam. E a lógica de fallback em modelos não é documentada. Pode não ser intencional, mas pelo menos eu costumava acreditar que é um uso legítimo até que aprendi a lógica de fallback nos modelos ...

@posva Obrigado por compartilhar sua experiência!

@Justineo Em sua opinião, você acha que este caso extremo causaria muita dor para os usuários ou é aceitável para a conveniência de melhorar a API de slot com escopo no Vue 2.x?

Isso foi movido para "Todo" - temos um consenso sobre o que implementar?

Estive pensando sobre isso na semana passada. Eu gosto da proposta de variável $slot @chrisvfritz e estava tentando resolver os bloqueios de implementação. Aqui está o que eu acho viável:

  • Todos os slots são compilados como funções (como no 3.0), então não há mais slots estáticos (isso leva a um rastreamento de atualização de componente mais preciso)
  • $scopedSlots exporá cada slot como uma função
  • $slots exporá cada slot como resultado da chamada da função correspondente com um objeto vazio.

Portanto, o seguinte seria equivalente:

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

Com as alterações internas acima, $slots e $scopedSlots são essencialmente unificados, mas devem ser compatíveis com versões anteriores! Isso também deve facilitar a migração para o 3.0.

Alguns exemplos de uso usando a variável $slot proposta:

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

Não acho que mesclar escopos aninhados seja uma boa ideia. Acho que é melhor ser explícito quando o aninhamento está envolvido (ou seja, sempre use slot-scope se você tiver slots de escopo aninhados):

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

Em seu último exemplo, a qual slot $slot se referindo?

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

@Akryum @Justineo hmm Posso dizer que isso pode ser confuso ... nós tornamos slot-scope utilizável no elemento raiz de um slot , ou seja, aqui foo é o escopo do slot fornecido por <foo/> , bar é fornecido por <bar/> e $slot é fornecido por <baz/> ... agora acho que foi um erro permitir tal uso, porque o uso mais intuitivo neste caso seria algo assim:

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

isso pode ser confuso

um uso mais intuitivo neste caso seria algo assim

Concordo absolutamente. Além disso, podemos continuar a usar a desestruturação.
Por exemplo:
`` `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>

Fechado via 7988a554 e 5d52262f

Resumo:

  • Suporta a variável $slot em todos os slots. (A presença de $slot faz com que o slot seja compilado como um slot com escopo definido).

  • Todos os slots, incluindo slots normais, agora estão expostos em this.$scopedSlots como funções. Isso significa supondo que this.$slots.default exista, this.$scopedSlots.default() retornará seu valor. Isso permite que os usuários da função de renderização sempre usem this.$scopedSlot e não se preocupem mais se o slot que está sendo passado tem o escopo definido ou não. Isso também é consistente com 3.0, onde todos os slots são expostos como funções (mas em this.$slots invés).

  • Sem alteração no uso de slot-scope , sem introdução de nova diretiva. Queremos fazer alterações de sintaxe mínimas e deixar a quebra potencial para 3.0.

@ yyx990803 $slot mescla todos os escopos de slot externo ou apenas se refere ao slot mais próximo?

@Justineo sem fusão, apenas o mais próximo.

Olhando para o uso de slots com escopo nos aplicativos aos quais tenho acesso, parece que a abreviação $slot não seria realmente utilizável em apenas metade dos casos sem o comportamento de mesclagem de slots aninhados. O problema é que os componentes básicos são normalmente usados ​​no lugar de elementos HTML brutos e esses componentes costumam ter seus próprios slots. Por exemplo:

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

No exemplo acima, há apenas um único slot com escopo, para o componente <MapMarkers> . <BaseIcon> aceita um slot sem escopo, mas como ele aceita um slot, $slot seria indefinido ou um objeto vazio sem o comportamento de fusão, forçando uma refatoração para o mais pesado:

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

Então, aqui estão minhas preocupações:

  • Pelo menos de minha própria amostragem, parece que _a maior parte_ do benefício de $slot poderia ser perdida se se referir apenas ao slot mais interno. Na verdade, pode até fazer mais mal do que bem, já que há mais API para as pessoas aprenderem.

  • Bibliotecas de IU podem começar a usar adereços com v-html onde os slots seriam mais apropriados, em resposta às reclamações do usuário sobre não poder usar $slot .

  • Os usuários podem começar a evitar componentes básicos em alguns casos, para manter o uso do slot com escopo mais elegante, levando a um código menos sustentável.

@ yyx990803 Sua principal preocupação com a fusão de $slot é que seria difícil dizer de quais dados de slot de componente vieram? Em caso afirmativo, pode ajudar saber que os únicos casos em que os aplicativos que tenho acesso usaram slots de escopo aninhados foram para componentes de provedor de estado, que, como mencionei anteriormente, descobri ser um antipadrão. Então, especialmente com propriedades de slot bem nomeadas, acho que os casos de ambigüidade real em casos de uso legítimos seriam muito raros. E com o comportamento de mesclagem automática, os usuários seriam capazes de decidir por si próprios se precisavam de mais explicitação - e quando o fizerem, slot-scope existiria para isso.

Como um meio-termo, aqui está uma alternativa potencial: e se, em vez de mesclar slots de escopo aninhados, fizéssemos $slot referir-se ao slot mais próximo _que forneceu um escopo_? Isso poderia eliminar os casos mais ambíguos, ao mesmo tempo em que abordaria minhas preocupações. Pensamentos?

O principal motivo para evitar a mesclagem é a não explicitação: apenas lendo o modelo, você realmente não sabe qual propriedade $slot vem de qual componente do provedor. Isso requer que você esteja totalmente ciente dos detalhes de implementação de cada componente que está olhando para ter certeza do que está acontecendo, e isso adiciona muita carga mental. Pode parecer bom no momento em que você está escrevendo, porque você tem todo o contexto em sua mente naquele momento, mas torna o código muito mais difícil de entender para um futuro leitor (seja você ou outro membro da equipe).

$ slot refere-se ao slot mais próximo que forneceu um escopo

Eu não acho que isso funcionaria. Isso leva ao mesmo problema: você precisa saber sobre os detalhes de implementação dos componentes para ter certeza do que está acontecendo. Eu diria que é ainda mais implícito e potencialmente confuso do que a fusão.


Em longo prazo, acho que a melhor solução é mudar a semântica de slot-scope para que se torne uma forma de criar um alias / desestruturar $slot . Portanto, seu exemplo seria:

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

Isso elimina a necessidade de mesclagem, é explícito sobre quais variáveis ​​vêm de qual provedor e não é excessivamente prolixo. (Acho que é isso que provavelmente faremos no 3.0)

O ponto estranho em que estamos agora é que permitimos que slot-scope fosse usado em não modelos, e agora isso nos impede de usá-lo no próprio componente.

Alterar sua semântica no 3.0 também pode causar muita confusão e problemas de migração.

Talvez possamos contornar o problema do while introduzindo uma nova propriedade, slot-alias , que faz exatamente o que diz - aliasing $slot para outra coisa. Ao contrário de slot-scope , ele só pode ser usado no próprio componente ou em um recipiente de slot de modelo (isso significa que funcionaria exatamente da mesma forma com slot-scope quando usado em um <template> ) :

Slots padrão aninhados:

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

Slots nomeados sem aninhamento:

O único caso em que inevitavelmente se torna prolixo é denominado slots com aninhamento:

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

(Na verdade, isso pode ser menos prolixo usando slot-scope E slot-alias , mas acho que pode ser um tanto confuso. Sou a favor de suspender slot-scope completo)

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

Por fim, podemos até usar uma abreviatura, () (já que em JSX os adereços de renderização geralmente são funções de seta que começam com () ):

<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 o acima com o JSX equivalente:

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

Fechando desde o projeto agora é muito diferente do que foi originalmente proposto. Abrindo um novo tópico.

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