# Телепорты

Узнайте как использовать телепорты на бесплатном уроке Vue School

Vue предлагает создавать свои пользовательские интерфейсы, инкапсулируя UI и соответствующее поведение в компоненты. Вкладывая их друг в друга можно построить дерево компонентов, из которого и строится пользовательский интерфейс приложения.

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

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

Рассмотрим следующую структуру HTML:

<body>
  <div style="position: relative;">
    <h3>Tooltips with Vue 3 Teleport</h3>
    <div>
      <modal-button></modal-button>
    </div>
  </div>
</body>
1
2
3
4
5
6
7
8

Давайте взглянем на modal-button.

Компонент содержит элемент button для открытия модального окна и элемент div с классом .modal, в котором будет располагаться содержимое модального окна, а также кнопка для его закрытия.

const app = Vue.createApp({});

app.component('modal-button', {
  template: `
    <button type="button" @click="modalOpen = true">
      Открыть полноэкранное модальное окно!
    </button>

    <div v-if="modalOpen" class="modal">
      <div>
        Информация в модальном окне!
        <button type="button" @click="modalOpen = false">
          Закрыть
        </button>
      </div>
    </div>
  `,
  data() {
    return {
      modalOpen: false
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

При использовании этого компонента внутри исходной структуры HTML видна проблема — модальное окно отрисовывается внутри глубоко вложенного div и его стиль position: absolute получает родителя относительно расположенного div по ссылке.

Телепорт предоставляет прозрачный способ для контроля, в каком месте DOM требуется отрисовать часть HTML, без необходимости использования глобального состояния или разделения на два компонента.

Давайте модифицируем modal-button для использования <teleport> и скажем Vue «телепортировать этот HTML в тег "body"».

app.component('modal-button', {
  template: `
    <button type="button" @click="modalOpen = true">
      Открыть полноэкранное модальное окно! (с помощью телепорта!)
    </button>

    <teleport to="body">
      <div v-if="modalOpen" class="modal">
        <div>
          Я телепортированное модальное окно!
          (Мой родитель "body")
          <button type="button" @click="modalOpen = false">
            Закрыть
          </button>
        </div>
      </div>
    </teleport>
  `,
  data() {
    return {
      modalOpen: false
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

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

See the Pen Vue 3 Teleport by Vue (@Vue) on CodePen.

# Использование с компонентами

Если <teleport> содержит компонент Vue, то он логически останется дочерним компонентом родителя <teleport>:

const app = Vue.createApp({
  template: `
    <h1>Корневой экземпляр</h1>
    <parent-component />
  `
})

app.component('parent-component', {
  template: `
    <h2>Это родительский компонент</h2>
    <teleport to="#endofbody">
      <child-component name="John" />
    </teleport>
  `
})

app.component('child-component', {
  props: ['name'],
  template: `
    <div>Привет, {{ name }}</div>
  `
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

В этом случае, даже когда child-component будет отрисован в другом месте, он останется потомком компонента parent-component и получит входной параметр name от него.

Это также означает, что инъекции из родительского компонента будут работать как и ожидается, и что дочерний компонент будет вложен ниже родительского компонента во Vue Devtools, вместо того, чтобы быть помещённым туда, куда он переместился фактически.

# Использование нескольких телепортов на одной цели

Подобный сценарий может быть при переиспользовании компонента <Modal>, когда может быть несколько его активных экземпляров одновременно. В таких случаях несколько компонентов <teleport> могут монтировать своё содержимое к одному и тому же элементу. Порядок будет определяться по времени добавления — более поздние будут располагаться после тех монтирований, что произошли раньше в целевом элементе.

<teleport to="#modals">
  <div>A</div>
</teleport>
<teleport to="#modals">
  <div>B</div>
</teleport>

<!-- результат -->
<div id="modals">
  <div>A</div>
  <div>B</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12

Подробнее опции компонента <teleport> можно изучить в справочнике API.

Deployed on Netlify.
Последнее обновление: 2020-12-12, 14:26:23 UTC