Vue: [Abandoned] RFC: Упростите использование слотов с заданной областью действия

Созданный на 11 дек. 2018  ·  36Комментарии  ·  Источник: vuejs/vue

Это продолжение https://github.com/vuejs/vue/issues/7740#issuecomment -371309357

Рациональный

Проблемы с использованием текущего слота с ограниченной областью действия:

  • Подробно при использовании <template slot-scope>
  • Ограничено одним элементом / компонентом, используется slot-scope непосредственно в элементе слота.

Предложение

Представьте новую директиву v-scope , которая может использоваться только для компонентов:

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

Он будет работать так же, как slot-scope для слота с заданной областью по умолчанию для <comp><comp> предоставляющим значение области). Таким образом, он также работает с деконструкцией:

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

Почему новая директива

Думаю, некоторое время назад команда кратко обсудила то, что я предложил, в https://github.com/vuejs/vue/issues/7740#issuecomment -371309357 в Slack, но я больше не мог найти запись чата. Вот причина новой директивы:

  • slot-scope был введен как специальный атрибут вместо директивы (атрибуты, начинающиеся с префикса v- ), потому что slot является атрибутом, и мы хотели сохранить согласованность атрибутов, связанных с слотами . slot в свою очередь, был введен как недирективный атрибут, потому что мы хотим, чтобы его использование отражало фактическое использование слотов в стандарте Shadow DOM. Мы подумали, что было бы лучше избегать использования нашего собственного параллельного v-slot когда в стандарте есть что-то концептуально то же самое.

  • Первоначально slot-scope был разработан для использования только с элементами <template> которые действуют как абстрактные контейнеры. Но это было многословно - поэтому мы ввели возможность использовать его непосредственно в элементе слота без оболочки <template> . Однако это также делает невозможным использование slot-scope непосредственно в самом компоненте, поскольку это может привести к неоднозначности, как показано здесь .

  • Я подумал о добавлении модификаторов или специальных префиксов в slot-scope , чтобы мы могли использовать их в компоненте напрямую, чтобы указать, что его содержимое слота должно рассматриваться как слот с заданной областью действия по умолчанию, но ни модификатор, ни префикс типа $ кажутся подходящими. Модификатор по замыслу должен применяться только к директивам, в то время как новый специальный синтаксис для одного варианта использования несовместим со всей структурой синтаксиса.

  • В течение очень долгого времени мы уклонялись от добавления дополнительных директив, часть из которых является синтаксисом шаблона - это то, что мы хотим сохранить как можно более стабильным, частью этого является то, что мы хотим свести основные директивы к минимуму и делать только вещи то, что пользователи не могут легко сделать в пользовательском пространстве. Однако в этом случае использование слота с ограниченной областью видимости достаточно важно, и я думаю, что новая директива может быть оправдана для того, чтобы сделать ее использование значительно менее шумным.

Обеспокоенность

  • Выражение, принимаемое v-scope , отличается от большинства других директив: оно ожидает временного имени переменной (которое также может быть деконструкцией), но не без приоритета: оно действует так же, как часть псевдонима v-for . Итак, концептуально v-scope попадает в тот же лагерь, что и v-for в качестве структурной директивы, которая создает временные переменные для своей внутренней области.

  • Это нарушит код пользователя, если у пользователя есть настраиваемая директива с именем v-scope которая используется в компоненте.

    • Поскольку пользовательские директивы в версии 2 в основном ориентированы на прямые манипуляции с DOM, относительно редко можно увидеть использование пользовательских директив для компонентов, тем более для того, что называется v-scope , поэтому влияние должно быть минимальным.

    • Даже в том случае, если это действительно происходит, с этим легко справиться, просто переименовав настраиваемую директиву.

discussion intend to implement

Самый полезный комментарий

Разрушая проблему

Пытаясь синтезировать, похоже, что нам нужно решение, которое:

  • уменьшить шаблон, необходимый для доступа к данным из слотов с заданной областью действия
  • Избегайте намека на то, что данные для слота по умолчанию доступны в именованных слотах
  • минимизировать количество новых API, которые мы должны ввести
  • не изобретайте заново API, если в спецификации веб-компонентов уже есть приемлемое решение.
  • оставайтесь ясными, чтобы не запутаться в том, откуда пришли данные

У меня может быть решение, которое решает все эти проблемы! 🤞 С $event для v-on у нас есть прецедент предоставления выражений, выполняемых внутри неявной функции с именованным аргументом. Людям нравится это удобство, и я не вижу, чтобы это могло вызвать путаницу, так что, возможно, нам стоит последовать этому примеру для слотов с ограниченным объемом.

Предложенное решение

Вместо использования v-scope мы могли бы сделать область слота доступной как $slot . Например:

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

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

Для контекста дочерний шаблон может выглядеть так:

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

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

Когда слоты вложены, объект $slot будет объединять данные слотов, причем самые внутренние слоты имеют приоритет переопределения. Например, в:

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

слияние может выглядеть так:

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

Вы можете беспокоиться / задумываться о том, как обрабатывать перекрытие пространств имен, и в 99,9% случаев я действительно не думаю, что это будет проблемой. Например:

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

В приведенном выше примере $slot.user всегда будет иметь правильное значение, несмотря на области вложенности. Однако иногда вам действительно нужен доступ к обоим свойствам одновременно, например:

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

В этих редких крайних случаях пользователи могут по-прежнему использовать slot-scope с его текущим поведением в качестве аварийного выхода . Но v-scope все равно будет ненужным, потому что, если я правильно понимаю, его цель будет заключаться в упрощении наиболее распространенных вариантов использования, которые объект $slot уже выполнил бы, не вызывая тех же проблем.

Преимущества

  • Когда пользователь хочет получить доступ к данным, предоставленным слотом, единственным шаблоном является $slot , который настолько краток, насколько это возможно, но остается явным. Сразу очевидно, что данные поступают из слота, без необходимости искать в компоненте, где определено свойство.

  • Чтобы знать, к каким данным они могут получить доступ, пользователям нужно только подумать о том, в какой (ые) слот (а) будет отображаться контент . Я думаю, что с v-scope было бы много путаницы с людьми, предполагающими, что он работает как v-for , потому что это единственная другая директива, которая определяет свойства контекста с областью видимости.

  • Пользователи не должны быть знакомы с деструктуризацией, чтобы использовать слоты с ограниченной областью видимости , что сокращает время обучения.

  • В некоторых случаях, особенно со многими вложенными слотами, которые все обеспечивают состояние, не будет очевидно, из какого_ компонента произошло какое-то состояние, и пользователям придется снова достичь slot-scope . Это может показаться недостатком, но когда я вижу вложенные слоты с ограниченной областью видимости, это почти всегда для шаблона поставщика состояния, который, как мне кажется, является анти-шаблоном. Вот мои рассуждения . Таким образом, тот факт, что многие слоты с вложенной областью видимости потребуют большего количества шаблонов, на самом деле может уменьшить использование антишаблонов .

  • Площадь поверхности API резко сокращается , потому что большинству разработчиков Vue нужно будет помнить только $slot , который является просто объектом.

  • Используя свойство с префиксом $ , мы строим API слотов веб-компонентов таким образом, чтобы прояснить, что $slot - это вещь Vue.

  • В настоящее время библиотеки часто делают что-то вроде <slot v-bind="user" /> чтобы их пользователи могли сохранить несколько символов с помощью slot-scope="user" вместо slot-scope="{ user } . На первый взгляд это кажется элегантным, но я пришел к выводу, что это антипаттерн. Проблема возникает, когда компонент хочет предоставить данные не пользователю. Затем у них есть два варианта: внести критические изменения в свой API или принудительно применить это новое свойство к объекту user , даже если это может иметь очень мало общего с пользователем. Ни то ни другое - не лучший вариант. К счастью, $slot устранит соблазн сделать компоненты менее ориентированными на будущее , потому что, хотя $slot по-прежнему короче $slot.user , вы теряете важный контекст из-за псевдонима пользователя как $slot .

Недостатки

  • В некоторых вложенных слотах с ограниченной областью видимости бывают крайние случаи, когда не будет очевидно, из какого_ компонента взяты данные. Тем не менее, в этих случаях пользователи все равно могут достигать slot-scope когда им нужна максимальная ясность, поэтому я не думаю, что это имеет большое значение. К тому же, как я упоминал ранее, я думаю, что очень вероятно, что пользователь стреляет себе в ногу с помощью компонентов поставщика состояний, если у них изначально есть эта проблема.

Все 36 Комментарий

Выглядит неплохо. Я думал об использовании аргумента для указания имени слота, но это позволило бы использовать несколько директив v scope для одного и того же компонента и, следовательно, повторно использовать контент, предоставленный компоненту. Который я не уверен, полезно ли это или может привести к проблемам

По большому счету, это улучшит использование api компонентов без рендеринга, которые со временем используются все чаще 🙌

Если я правильно понимаю, v-scope обслуживает только один вариант использования. Вместо того, чтобы писать:

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

Мы можем написать:

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

В этом случае он действительно значительно снизил шум, но, похоже, это единственный случай. Представление новой директивы (которая работает совершенно иначе, чем другие директивы, даже из v-for потому что она работает только с компонентами и полагается на логику <slot> ниже), кажется излишним. Еще одна проблема заключается в том, что когда я ищу v-scope в этом репо , только два случая говорят об уменьшении объема данных поддерева шаблона (https://github.com/vuejs/vue/issues/5269 # issuecomment-288912328, https://github.com/vuejs/vue/issues/6913), например, как with работает в JavaScript.

Мне это нравится. У меня остается один вопрос: как мы хотим работать с именованными слотами. Если мы хотим разрешить именованным слотам использовать одну и ту же директиву, нам нужно будет разрешить это и для <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>

Мы могли бы использовать эту возможность, чтобы отказаться от sot-scope и удалить его в Vue 3, заменив его на v-scope

Я думаю, что нам определенно не следует использовать две разные концепции (директива v-scope и slot-scope attibute) для одного и того же.

Примечание: теперь, глядя на это, люди могут получить впечатление от иерархии элементов и
директивы, что они могут получить доступ к otherScope и scope в названном слоте. Может быть обратной стороной.

@LinusBorg для имени, аргумент может делать v-scope:slotName . Я думаю, что смысл разрешать это только для компонентов - это заменить то, что сказал @Justineo (https://github.com/vuejs/vue/issues/9180#issuecomment-446168296)

Примечание: теперь, глядя на это, люди могут получить впечатление от иерархии элементов и
директивы, чтобы они могли получить доступ к otherScope и scope в названном слоте. Может быть обратной стороной.

Это работает, правда? 🤔 Я почти уверен, что у меня есть вложенные прицелы слотов

Это работает, правда? 🤔 Я почти уверен, что у меня есть вложенные прицелы слотов

Я не вкладывал их в гнездо, это слоты для братьев и сестер. Я определил область слота по умолчанию с помощью v-scope на компоненте, а именованный слот (который является слотом- братом ) с помощью <template slot="someName">
Видеть? Это сбивает с толку ^^

@LinusBorg Я думаю, это сбивает с толку. Думаю, концептуально это:

  • Когда v-scope используется непосредственно в компоненте, это означает, что вы используете только слот по умолчанию.
  • Если вы хотите использовать именованные слоты ... вам все равно нужно использовать slot + slot-scope .

Я согласен с тем, что наличие v-scope и slot-scope может быть непоследовательным / запутанным, особенно для новых пользователей, не знающих, как мы пришли к текущему дизайну.

Разрушая проблему

Пытаясь синтезировать, похоже, что нам нужно решение, которое:

  • уменьшить шаблон, необходимый для доступа к данным из слотов с заданной областью действия
  • Избегайте намека на то, что данные для слота по умолчанию доступны в именованных слотах
  • минимизировать количество новых API, которые мы должны ввести
  • не изобретайте заново API, если в спецификации веб-компонентов уже есть приемлемое решение.
  • оставайтесь ясными, чтобы не запутаться в том, откуда пришли данные

У меня может быть решение, которое решает все эти проблемы! 🤞 С $event для v-on у нас есть прецедент предоставления выражений, выполняемых внутри неявной функции с именованным аргументом. Людям нравится это удобство, и я не вижу, чтобы это могло вызвать путаницу, так что, возможно, нам стоит последовать этому примеру для слотов с ограниченным объемом.

Предложенное решение

Вместо использования v-scope мы могли бы сделать область слота доступной как $slot . Например:

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

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

Для контекста дочерний шаблон может выглядеть так:

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

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

Когда слоты вложены, объект $slot будет объединять данные слотов, причем самые внутренние слоты имеют приоритет переопределения. Например, в:

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

слияние может выглядеть так:

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

Вы можете беспокоиться / задумываться о том, как обрабатывать перекрытие пространств имен, и в 99,9% случаев я действительно не думаю, что это будет проблемой. Например:

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

В приведенном выше примере $slot.user всегда будет иметь правильное значение, несмотря на области вложенности. Однако иногда вам действительно нужен доступ к обоим свойствам одновременно, например:

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

В этих редких крайних случаях пользователи могут по-прежнему использовать slot-scope с его текущим поведением в качестве аварийного выхода . Но v-scope все равно будет ненужным, потому что, если я правильно понимаю, его цель будет заключаться в упрощении наиболее распространенных вариантов использования, которые объект $slot уже выполнил бы, не вызывая тех же проблем.

Преимущества

  • Когда пользователь хочет получить доступ к данным, предоставленным слотом, единственным шаблоном является $slot , который настолько краток, насколько это возможно, но остается явным. Сразу очевидно, что данные поступают из слота, без необходимости искать в компоненте, где определено свойство.

  • Чтобы знать, к каким данным они могут получить доступ, пользователям нужно только подумать о том, в какой (ые) слот (а) будет отображаться контент . Я думаю, что с v-scope было бы много путаницы с людьми, предполагающими, что он работает как v-for , потому что это единственная другая директива, которая определяет свойства контекста с областью видимости.

  • Пользователи не должны быть знакомы с деструктуризацией, чтобы использовать слоты с ограниченной областью видимости , что сокращает время обучения.

  • В некоторых случаях, особенно со многими вложенными слотами, которые все обеспечивают состояние, не будет очевидно, из какого_ компонента произошло какое-то состояние, и пользователям придется снова достичь slot-scope . Это может показаться недостатком, но когда я вижу вложенные слоты с ограниченной областью видимости, это почти всегда для шаблона поставщика состояния, который, как мне кажется, является анти-шаблоном. Вот мои рассуждения . Таким образом, тот факт, что многие слоты с вложенной областью видимости потребуют большего количества шаблонов, на самом деле может уменьшить использование антишаблонов .

  • Площадь поверхности API резко сокращается , потому что большинству разработчиков Vue нужно будет помнить только $slot , который является просто объектом.

  • Используя свойство с префиксом $ , мы строим API слотов веб-компонентов таким образом, чтобы прояснить, что $slot - это вещь Vue.

  • В настоящее время библиотеки часто делают что-то вроде <slot v-bind="user" /> чтобы их пользователи могли сохранить несколько символов с помощью slot-scope="user" вместо slot-scope="{ user } . На первый взгляд это кажется элегантным, но я пришел к выводу, что это антипаттерн. Проблема возникает, когда компонент хочет предоставить данные не пользователю. Затем у них есть два варианта: внести критические изменения в свой API или принудительно применить это новое свойство к объекту user , даже если это может иметь очень мало общего с пользователем. Ни то ни другое - не лучший вариант. К счастью, $slot устранит соблазн сделать компоненты менее ориентированными на будущее , потому что, хотя $slot по-прежнему короче $slot.user , вы теряете важный контекст из-за псевдонима пользователя как $slot .

Недостатки

  • В некоторых вложенных слотах с ограниченной областью видимости бывают крайние случаи, когда не будет очевидно, из какого_ компонента взяты данные. Тем не менее, в этих случаях пользователи все равно могут достигать slot-scope когда им нужна максимальная ясность, поэтому я не думаю, что это имеет большое значение. К тому же, как я упоминал ранее, я думаю, что очень вероятно, что пользователь стреляет себе в ногу с помощью компонентов поставщика состояний, если у них изначально есть эта проблема.

Я думаю, что свойство с префиксом $ должно быть согласованным внутри всего экземпляра Vue. Я не уверен, вызовет ли это путаницу, неявно вводя $slot в каждый слот, и они фактически означают локальный аргумент внутри этих областей видимости слота. Это своего рода соглашение (согласно моим наблюдениям), что свойства с префиксом $ обозначают что-то доступное для каждого экземпляра Vue, поэтому их можно использовать внутри где угодно, включая хуки жизненного цикла, методы и функции рендеринга. Добавление этого специального $slot нарушит это соглашение.

@chrisvfritz, это интересное предложение, но оно как бы полагается на унификацию обычных слотов и слотов с ограниченным объемом (так что, возможно, что-то, что можно будет рассмотреть в v3). Самая большая проблема с этим заключается в том, должно ли содержимое слота быть доступно в дочернем компоненте $slots или $scopedSlots ? Единственный намек - присутствие $slot где-нибудь в выражениях (может быть где угодно вниз по дереву), что не так явно, как slot-scope (которое может быть только в корне слота). Хотя технически мы можем обнаружить это в компиляторе, для пользователя слот будет перемещаться с $slots на $scopedSlots всякий раз, когда пользователь начинает использовать $slot в шаблоне ...

Будет ли это иметь большое влияние на пользователей JSX?

@donnysim нет, это никак не влияет на JSX.

Я думаю, что свойство с префиксом $ должно быть согласованным внутри всего экземпляра Vue. Я не уверен, вызовет ли это путаницу, неявно вводя $slot в каждый слот, и они фактически означают локальный аргумент внутри этих областей видимости слота.

@Justineo Поначалу у меня была такая же мысль, но мы делаем это для $event и никто, кажется, не жалуется, так что уже есть _is_ прецедент, и у нас есть доказательства того, что пользователи обычно не сбиты с толку и действительно наслаждаются удобством.

Самая большая проблема с этим заключается в том, должно ли содержимое слота быть доступно в дочернем компоненте $slots или $scopedSlots ?

@ yyx990803 Отличный вопрос! У меня есть идея, которая может сработать. Что, если в случаях, когда это неоднозначно (вложенные слоты с использованием $slot ), мы просто компилируем все слоты в слот с заданной областью, но также делаем все $scopedSlots доступными в $slots качестве геттеров? Например, что-то вроде:

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

Таким образом, в случае:

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

Мы могли бы скомпилировать что-то вроде:

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

И не имеет значения, происходит ли foo из <A> или <B> , потому что внутри этих компонентов будут работать как this.$slots.default и this.$scopedSlots.default(someData) .

Пара предостережений:

  • В случаях с вложенными слотами, и только некоторые из них фактически имеют область видимости, скомпилированные функции рендеринга из шаблонов с использованием $slot будут немного больше, чем при использовании slot-scope . Хотя это не очень важно, так что я думаю, что это нормально.

  • Я не могу вспомнить реальный пример, но могут быть крайние случаи, когда пользователь повторяет $slots / $scopedSlots и наличие новых или повторяющихся свойств может привести к неожиданному поведению. Я перебирал динамически именованные $slots и $scopedSlots раньше, чтобы включить некоторые интересные шаблоны, но это изменение не повлияет ни на один из вариантов использования, с которыми я столкнулся.

Мысли?

мы делаем это за $event и, кажется, никто не жалуется

@chrisvfritz О, я пропустил $event штуку. Хотя это явно исключение из соглашения (я полагаю, что это 😅), $event доступен только в очень ограниченной области (только внутри литералов атрибутов v-bind и без вложенности).

А для проксирования $scopedSlots на $slots :

Раньше я считал, что слоты и слоты с ограниченной областью видимости - это разные концепции, и Vue может использовать их по-разному, а это значит, что у меня может быть и слот, и слот с ограниченной областью видимости с одним и тем же именем, но служат разным целям. Но позже я обнаружил, что Vue фактически откатывается к слоту с тем же именем, когда указанный слот с заданной областью недоступен. Для меня это означает, что мы должны рассматривать их как одно и то же, но поскольку у нас есть и $slots и $scopedSlots доступные для каждого экземпляра, мы всегда можем использовать их в функциях рендеринга более гибким / неожиданным способом. , например, использование слота по умолчанию для переопределения всех элементов списка и слота с заданной по умолчанию области действия для переопределения одного элемента. На самом деле это не поощряется (мы не предлагали рекомендуемое использование этого в наших документах и ​​руководстве по стилю AFAIK), так как мы, вероятно, объединим их в единую концепцию слота в версии 3.0, но это приведет к поломке некоторое использование разрешено с довольно долгого времени.

мы, вероятно, объединим их в единую концепцию слота в версии 3.0, но если сделать это в версии 2.x, это нарушит некоторые возможности использования, разрешенные с давних пор.

@Justineo Я могу неправильно понять, но я не предлагаю уже объединять их в 2.x - просто расширяю текущее резервное поведение. Описанный вами вариант использования, когда и $slots и $scopedSlots используют одно и то же пространство имен, по-прежнему возможен, потому что поведение slot-scope останется неизменным. Например:

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

все равно будет работать точно так же. Имеет ли это смысл?

@chrisvfritz

Я не говорил о слиянии их в 2.x. Я говорил, если вы сделаете это:

сделать все $scopedSlots доступными в $slots качестве геттеров

Вы больше не можете отличить $slots.foo от $scopedSlots.foo и использовать их отдельно в функциях рендеринга.

Вы больше не можете отличить $ slots.foo от $ scopedSlots.foo и использовать их отдельно в функциях рендеринга.

Честно говоря, я был бы счастлив, если бы все (слоты и scopedSlots) были доступны в scopedSlots как функции, это просто означает, что больше не будет бесполезных проверок, какой слот отображать. С точки зрения компонентов действительно не имеет значения, какой тип слота использует разработчик. Исходя из смешанного синтаксиса JSX и шаблона.

@donnysim Да, я согласен с вами, что их следует объединить. Я объяснил, почему меня так беспокоит, в https://github.com/vuejs/vue/issues/9180#issuecomment -447185512. Речь идет об обратной совместимости.

Вы больше не можете отличить $slots.foo от $scopedSlots.foo и использовать их отдельно в функциях рендеринга.

@Justineo На самом деле вы _можете_, если мы сначала обрабатываем $slots и не заменяем уже определенные слоты. См. Пример реализации, который я опубликовал выше:

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

Рассмотрим следующий пример:

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

Если пользователь компонента передал только vm.$scopedSlots.default , он должен пройти через this.items и отобразить item s в слот с заданной областью. Но теперь, когда vm.$scopedSlot.default является слотом с ограниченной областью видимости и он существует, слот с ограниченной областью видимости будет вызываться вместо слота.

@Justineo Я думаю, что этот вариант использования будет очень редким, но шаблон все равно можно будет изменить:

if (!this.$slots.default) {

к:

if (this.$scopedSlots.default) {

или, если пользователь иногда предоставляет слот с ограниченной областью видимости, который по какой-то причине должен игнорироваться:

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

Хотя я бы все равно не стал считать это переломным моментом. Во-первых, мы никогда не документировали / не рекомендовали использование одноименных и ограниченных слотов, поэтому это никогда не было частью публичного контракта. Во-вторых, как вы упомянули, слоты с ограниченной областью видимости в шаблонах уже возвращаются к слоту без области видимости с тем же именем, что служит хорошим историческим свидетельством того, что мы никогда не предполагали, что граница с областью действия / без области видимости будет использоваться таким образом.

Имеет ли это смысл? Кроме того, видели ли вы какие-либо реальные варианты использования повторного использования названий слотов? Или вы можете придумать что-нибудь? Я не могу, но если есть_ полезные и уникальные шаблоны, которые он позволяет, было бы хорошо узнать о них сейчас, потому что это также повлияет на наше решение объединить их в Vue 3.

Я думаю, что слоты с ограниченным диапазоном и одноименные слоты для разных целей - это
плохая идея. Я использовал это в предварительной версии Vue и удалил его.
потому что это сбивало с толку. И теперь мне нужно проверить оба слота по умолчанию
и слот с областью видимости, чтобы разработчик мог предоставить слот, не потребляя данные.
И если я правильно помню, у вас не может быть того же имени при использовании
шаблоны.

@chrisvfritz

Я не говорил, что этот шаблон полезен и его следует поддерживать. Это действительно может вызвать путаницу. Я просто сказал, что можно сломать существующий код. Мы никогда не документировали использование одного и того же имени между слотами и слотами с заданной областью действия, но мы никогда не советуем пользователям этого не делать. И логика отката в шаблонах не документирована. Возможно, это было не намеренно, но, по крайней мере, я сам считал, что это законное использование, пока не изучил логику отката в шаблонах ...

@posva Спасибо, что поделились своим опытом!

@Justineo По вашему мнению, вы чувствуете, что этот

Это было перемещено в «Todo» - есть ли у нас консенсус по поводу того, что реализовать?

Я думал об этом на прошлой неделе. Мне нравится предложение переменной $slot @chrisvfritz, и я пытался разработать блокираторы реализации. Вот что я считаю возможным:

  • Все слоты скомпилированы как функции (как в версии 3.0), поэтому больше никаких статических слотов (это приводит к более точному отслеживанию обновлений компонентов)
  • $scopedSlots будет отображать каждый слот как функцию
  • $slots откроет каждый слот в результате вызова соответствующей функции с пустым объектом.

Таким образом, следующее будет эквивалентно:

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

С указанными выше внутренними изменениями $slots и $scopedSlots по существу унифицированы, но должны быть обратно совместимы! Это также должно упростить переход на 3.0.

Некоторые примеры использования с использованием предложенной переменной $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>

Я не думаю, что объединение вложенных областей видимости - хорошая идея. Я думаю, что лучше быть явным, когда задействовано вложение (т.е. всегда используйте slot-scope если у вас есть вложенные слоты с областью видимости):

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

В последнем примере, к какому слоту относится $slot ?

@ Акрюм, это { ...foo, ...bar } .

@Akryum @Justineo хм, я могу сказать, что это может сбивать с толку ... мы сделали slot-scope доступным для корневого элемента слота , т.е. здесь foo - это область видимости слота, предоставляемая <foo/> , bar предоставляется <bar/> а $slot предоставляется <baz/> ... теперь я думаю, что было ошибкой разрешить такое использование, потому что более интуитивно понятное использование в этом случае будет примерно таким:

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

это могло сбивать с толку

более интуитивно понятное использование в этом случае будет примерно таким

Абсолютно согласен. Также мы можем продолжать использовать деструктуризацию.
Например:
`` 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>

Закрыт через 7988a554 и 5d52262f

Резюме:

  • Поддержка переменной $slot во всех слотах. (Наличие $slot заставляет слот компилироваться как слот с ограниченной областью действия).

  • Все слоты, включая обычные слоты, теперь представлены в this.$scopedSlots как функции. Это означает, что если this.$slots.default существует, this.$scopedSlots.default() вернет его значение. Это позволяет пользователям функции рендеринга всегда использовать this.$scopedSlot и больше не беспокоиться о том, является ли переданный слот ограниченным или нет. Это также согласуется с версией 3.0, где все слоты представлены как функции (но вместо этого в this.$slots ).

  • Без изменений в использовании slot-scope , без введения новой директивы. Мы хотим сделать изменения синтаксиса минимальными и оставить потенциальную поломку для версии 3.0.

@ yyx990803 Объединяет ли $slot все области внешнего слота или просто ссылается на ближайший слот?

@Justineo, никаких слияний, только самое близкое.

Глядя на использование слотов с ограниченной областью видимости в приложениях, к которым у меня есть доступ, кажется, что сокращение $slot самом деле не будет использоваться чуть более чем в половине случаев без поведения слияния вложенных слотов. Проблема в том, что базовые компоненты обычно используются вместо необработанных HTML-элементов, и у этих компонентов часто есть свои собственные слоты. Например:

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

В приведенном выше примере для компонента <MapMarkers> есть только один слот с ограниченной областью действия. <BaseIcon> принимает слот без области видимости, но поскольку он принимает слот вообще, $slot будет неопределенным или пустым объектом без поведения слияния, что вынуждает выполнить рефакторинг до более грубого:

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

Итак, вот мои опасения:

  • По крайней мере, исходя из моей собственной выборки, кажется, что _ большая__ польза от $slot может быть потеряна, если она относится только к самому внутреннему слоту. Фактически, это может даже принести больше вреда, чем пользы, поскольку людям нужно изучить больше API.

  • Библиотеки пользовательского интерфейса могут начать чрезмерно использовать реквизиты с v-html где слоты будут более подходящими, в ответ на жалобы пользователей на невозможность использования $slot .

  • В некоторых случаях пользователи могут начать избегать базовых компонентов, чтобы сохранить более элегантное использование слотов с ограниченной областью видимости, что приведет к менее удобному в сопровождении коду.

@ yyx990803 Вас больше всего беспокоит слияние $slot что было бы трудно определить, из какого слота компонента были взяты данные? Если это так, может быть полезно знать, что единственные случаи, когда приложения, к которым у меня есть доступ, использовали вложенные слоты с ограниченной областью видимости, были для компонентов поставщика состояния, которые, как я уже упоминал ранее, я обнаружил как анти-шаблон. Поэтому, особенно с хорошо названными свойствами слота, я думаю, что случаи реальной двусмысленности в законных вариантах использования будут очень редкими. А с помощью автоматического слияния пользователи смогут сами решить, нужна ли им большая ясность - и когда они это сделают, то для этого и будет существовать slot-scope .

Однако в качестве компромисса есть потенциальная альтернатива: что, если бы вместо объединения вложенных слотов с заданной областью действия мы использовали $slot ссылки на ближайший слот _, который обеспечивал область действия_? Это могло бы устранить самые двусмысленные случаи, но при этом решить мои проблемы. Мысли?

Основная причина, по которой следует избегать слияния, - это неявность: просто читая шаблон, вы действительно не знаете, какое свойство $slot происходит от какого компонента поставщика. Это требует, чтобы вы были полностью осведомлены о деталях реализации каждого компонента, на который вы смотрите, чтобы быть уверенным в том, что происходит, а это добавляет много умственной нагрузки. Это может звучать хорошо в тот момент, когда вы его пишете, потому что в этот момент у вас в голове есть полный контекст, но это делает код намного более трудным для понимания для будущего читателя (будь то вы или другой член команды).

$ slot относится к ближайшему слоту, который предоставил область видимости

Не думаю, что это сработает. Это приводит к той же проблеме: вам нужно знать детали реализации компонентов, чтобы быть уверенным в том, что происходит. Я бы сказал, что это даже более неявно и потенциально запутанно, чем слияние.


В долгосрочной перспективе я думаю, что лучшим решением является изменение семантики slot-scope чтобы он стал способом псевдонима / деструктуризации $slot . Итак, ваш пример будет выглядеть так:

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

Это устраняет необходимость слияния, четко определяет, какие переменные поступают от какого провайдера, и не является чрезмерно подробным. (Я думаю, что это то, что мы, вероятно, собираемся сделать в версии 3.0)

Неловкое положение, в котором мы сейчас находимся, заключается в том, что мы разрешили использовать slot-scope для не-шаблонов, и теперь это не позволяет нам использовать его в самом компоненте.

Изменение его семантики в версии 3.0 также может привести к путанице и боли при миграции.

Возможно, мы сможем обойти проблему while, введя новое свойство slot-alias , которое делает именно то, что написано - псевдоним $slot на что-то еще. В отличие от slot-scope , его можно использовать только в самом компоненте или в контейнере слота шаблона (это означает, что он будет работать точно так же с slot-scope при использовании в <template> ) :

Вложенные слоты по умолчанию:

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

Именованные слоты без вложенности:

Единственный случай, когда он неизбежно становится многословным, - это именованные слоты с вложенностью:

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

(На самом деле это может быть менее подробным, если использовать как slot-scope и slot-alias , но я думаю, что это может сбить с толку. Я за то, чтобы полностью отказаться от slot-scope )

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

Наконец, мы можем даже дать ему сокращенное обозначение, () (поскольку в JSX рендеринге реквизиты обычно являются стрелочными функциями, которые начинаются с () ):

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

Сравните приведенное выше с эквивалентным JSX:

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

В заключение, поскольку дизайн сейчас сильно отличается от того, что было предложено изначально. Вместо этого открывайте новую ветку.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги