# Teleport
Vue は私たちに、UI やそれに関連する挙動をコンポーネントにして、カプセル化することで UI を作り上げることを勧めています。私たちはそれらを互いに入れ子にして、アプリケーションを構成するツリーを作ることができます。
しかしながら、コンポーネントのテンプレートの一部が、論理的にこのコンポーネントに属している場合もありますが、技術的な観点では、テンプレートのこの部分を Vue アプリの外や、DOM 内の別の場所に移動させることが望ましいこともあります。
これの一般的なシナリオは、フルスクリーンのモーダルを含むコンポーネントです。多くの場合、モーダルのロジックはコンポーネント内に残したいと思うでしょうが、モーダルの配置はすぐに CSS で解決するのが難しくなったり、コンポーネントの構成を変更する必要が出てくるでしょう。
以下のような HTML 構造を考えてみましょう。
<body>
<div style="position: relative;">
<h3>Tooltips with Vue 3 Teleport</h3>
<div>
<modal-button></modal-button>
</div>
</div>
</body>
2
3
4
5
6
7
8
modal-button
について見てみましょう。
コンポーネントはモーダルを開くための button
要素を持っており、さらに .modal
クラスを持った div
要素があります。これはモーダルのコンテンツを表示して、自身を閉じるためのボタンを持っている要素です。
const app = Vue.createApp({});
app.component('modal-button', {
template: `
<button @click="modalOpen = true">
Open full screen modal!
</button>
<div v-if="modalOpen" class="modal">
<div>
I'm a modal!
<button @click="modalOpen = false">
Close
</button>
</div>
</div>
`,
data() {
return {
modalOpen: false
}
}
})
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
を参照として使用します。
Teleport は、グローバルステートに頼ったり、2つのコンポーネントに分割しなくても、HTML の一部を DOM のどの親の下でレンダリングするかを制御するための、きれいな方法を提供します。
<teleport>
を使って、Vue にこの HTML を "body" タグに "Teleport (テレポート) " させるよう、modal-button
を変更しましょう。
app.component('modal-button', {
template: `
<button @click="modalOpen = true">
Open full screen modal! (With teleport!)
</button>
<teleport to="body">
<div v-if="modalOpen" class="modal">
<div>
I'm a teleported modal!
(My parent is "body")
<button @click="modalOpen = false">
Close
</button>
</div>
</div>
</teleport>
`,
data() {
return {
modalOpen: false
}
}
})
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
タグの子として正しくレンダリングします。
# Vue コンポーネントと使う
もし <teleport>
が Vue コンポーネントを含む場合、それは<teleport>
の親の、論理的な子コンポーネントのままになります。
const app = Vue.createApp({
template: `
<h1>Root instance</h1>
<parent-component />
`
})
app.component('parent-component', {
template: `
<h2>This is a parent component</h2>
<teleport to="#endofbody">
<child-component name="John" />
</teleport>
`
})
app.component('child-component', {
props: ['name'],
template: `
<div>Hello, {{ name }}</div>
`
})
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 上では実際のコンテンツが移動した場所に配置されるのではなく、親コンポーネントの下に配置されることを意味します。
# 複数の Teleport のターゲットを同じにして使う
一般的なユースケースのシナリオは、再利用可能な <Modal>
コンポーネントで、同時に複数のインスタンスがアクティブになっている状態かもしれません。このようなシナリオの場合、複数の <teleport>
は、同じターゲット要素に対してそれぞれのコンテンツをマウントすることができます。
<teleport to="#modals">
<div>A</div>
</teleport>
<teleport to="#modals">
<div>B</div>
</teleport>
<!-- result-->
<div id="modals">
<div>A</div>
<div>B</div>
</div>
2
3
4
5
6
7
8
9
10
11
12
<teleport>
コンポーネントのオプションは API リファレンス で確認できます。
← カスタムディレクティブ Render 関数 →