Это продолжение 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
, поэтому влияние должно быть минимальным.
Даже в том случае, если это действительно происходит, с этим легко справиться, просто переименовав настраиваемую директиву.
Выглядит неплохо. Я думал об использовании аргумента для указания имени слота, но это позволило бы использовать несколько директив 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
может быть непоследовательным / запутанным, особенно для новых пользователей, не знающих, как мы пришли к текущему дизайну.
Пытаясь синтезировать, похоже, что нам нужно решение, которое:
У меня может быть решение, которое решает все эти проблемы! 🤞 С $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, и я пытался разработать блокираторы реализации. Вот что я считаю возможным:
$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>
В заключение, поскольку дизайн сейчас сильно отличается от того, что было предложено изначально. Вместо этого открывайте новую ветку.
Самый полезный комментарий
Разрушая проблему
Пытаясь синтезировать, похоже, что нам нужно решение, которое:
У меня может быть решение, которое решает все эти проблемы! 🤞 С
$event
дляv-on
у нас есть прецедент предоставления выражений, выполняемых внутри неявной функции с именованным аргументом. Людям нравится это удобство, и я не вижу, чтобы это могло вызвать путаницу, так что, возможно, нам стоит последовать этому примеру для слотов с ограниченным объемом.Предложенное решение
Вместо использования
v-scope
мы могли бы сделать область слота доступной как$slot
. Например:Для контекста дочерний шаблон может выглядеть так:
Когда слоты вложены, объект
$slot
будет объединять данные слотов, причем самые внутренние слоты имеют приоритет переопределения. Например, в:слияние может выглядеть так:
Вы можете беспокоиться / задумываться о том, как обрабатывать перекрытие пространств имен, и в 99,9% случаев я действительно не думаю, что это будет проблемой. Например:
В приведенном выше примере
$slot.user
всегда будет иметь правильное значение, несмотря на области вложенности. Однако иногда вам действительно нужен доступ к обоим свойствам одновременно, например:В этих редких крайних случаях пользователи могут по-прежнему использовать
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
когда им нужна максимальная ясность, поэтому я не думаю, что это имеет большое значение. К тому же, как я упоминал ранее, я думаю, что очень вероятно, что пользователь стреляет себе в ногу с помощью компонентов поставщика состояний, если у них изначально есть эта проблема.