При создании и росте сложного SPA приложения построить хорошую архитектуру становится все труднее. Порой данные, которые доступны в одном компоненте нужно отобразить в другом компоненте.
Представим себе список проблем (issues) как на github и в уголке страницы значок с числом не просмотренных проблем. Когда пользователь заходит в просмотр одной из проблем, то в списке она меняет вид, так как стала прочитанной. При этом и значок с числом должен уменьшить его. И так как это разные компоненты, которые могут быть не вложены один в другой, то встанет вопрос откуда брать данные для обои. “Vuex”, – скажите вы. Но не всегда это будет удобно и верно.
PorttalVue предлагает возможность шаблон одного компонента, который использует доступные ему данные отобразить где-нибудь в другом компоненте. Или даже вовсе вне элемента, в котором выводится ваше Vue приложение.
Простой пример выглядит так:
<!-- This is inside in some component -->
<portal to="destination">
<p>This slot content will be rendered wherever the
<portal-target> with name 'destination'
is located.
</p>
</portal>
<!-- ... -->
<!-- This is somewhere in your vue application -->
<portal-target name="destination">
<!--
This component can be located anwhere in your App
(i.e. right before the </body> tag, good for overlays).
The slot content of the above portal component will be rendered here.
-->
</portal-target>
Один компонент-донор может содержать несколько таких порталов (шаблонов для вставки в другие компоненты). Вывод можно также делать по условию через v-if.
Как было упомянуто выше такой шаблон можно отобразить где-нибудь вне разметки vue приложения. Это полезно, если ваше приложение занимает не всю страницу, а какой-нибудь ее кусочек в legacy проекте. В этом случае можно прибегнуть к следующей конструкции:
<div id="app">
<MountingPortal mountTo="#widget" name="source" append>
<p>Content for the Target</p>
</MountingPortal>
<div>
<script>
new Vue({el: '#app'})
</script>
<aside id="widget" class="widget-sidebar">
This Element is not controlled by our Vue-App,
but we can create a <portal-target> here with <MountingPortal>.
</aside>
Здесь используется компонент MountingPortal вместо portal, чтобы вывести содержимое в блоке с селектором #widget.
PortalVue можно также использовать вместе со Scoped Slots. Это означает, что вы можете отправить в принимающий блок (PortalTarget) кусочек шаблона, который будет иметь возможность передать туда параметры (props):
<portal to="destination">
<p slot-scope="{message}">{{message}}</p>
</portal>
<portal-target
name="destination"
:slot-props="{message: 'Hello from the Target to You!'}"
/>
Результат:
<div class="vue-portal-target">
<p>Hello from the Target to You!</p>
</div>
В каких типичных случаях может пригодиться PortalVue?
- Позиционирование модальных диалоговых окон и других компонентов отображаемых поверх страницы (Positioning Modals & Overlays), чтобы стили родительского элемента не влияли на дочерний и чтобы не требовалось использовать z-index
- Если Vue приложение контролирует только часть вашей страницы, а некоторый шаблон вы хотите отобразить в другом конце страницы.
PortalVue работает на Vue 2, а для Vue 3 такая возможность есть из коробки. Называется Teleport
Полезные ссылки: