Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: 3.5: useTemplateRef() #2311

Merged
merged 7 commits into from
Sep 20, 2024
Merged
79 changes: 77 additions & 2 deletions src/guide/essentials/template-refs.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,31 @@ Vue の宣言型レンダリングモデルは、直接的な DOM 操作のほ

<div class="composition-api">

Composition API で参照を取得するには、以下のようにテンプレートの ref 属性の値と一致する名前の ref を宣言します:
Composition API で参照を取得するには、[`useTemplateRef()`](/api/composition-api-helpers#usetemplateref) ヘルパー <sup class="vt-badge" data-text="3.5+" /> を使用できます:

```vue
<script setup>
import { useTemplateRef, onMounted } from 'vue'

// 最初の引数は、テンプレートの ref の値に一致させる必要があります。
const input = useTemplateRef('my-input')

onMounted(() => {
input.value.focus()
})
</script>

<template>
<input ref="my-input" />
</template>
```

TypeScript を使用する場合、Vue の IDE サポートと `vue-tsc` は、一致する `ref` 属性が使用されている要素やコンポーネントに基づいて、`inputRef.value` の型を自動的に推論します。

<details>
<summary>3.5 以前の使用方法</summary>

`useTemplateRef()` が導入されていなかった 3.5 以前のバージョンでは、テンプレート内の `ref` 属性の値と同じ名前で `ref` を宣言する必要がありました:

```vue
<script setup>
Expand Down Expand Up @@ -46,6 +70,8 @@ export default {
}
```

</details>

</div>
<div class="options-api">

Expand Down Expand Up @@ -95,6 +121,33 @@ watchEffect(() => {

`v-for` の中で `ref` を使用すると、対応する参照には配列値が格納されます。そしてこの配列値には、マウント後の要素が代入されます:

```vue
<script setup>
import { ref, useTemplateRef, onMounted } from 'vue'

const list = ref([
/* ... */
])

const itemRefs = useTemplateRef('items')

onMounted(() => console.log(itemRefs.value))
</script>

<template>
<ul>
<li v-for="item in list" ref="items">
{{ item }}
</li>
</ul>
</template>
```

[Playground で試す](https://play.vuejs.org/#eNp9UsluwjAQ/ZWRLwQpDepyQoDUIg6t1EWUW91DFAZq6tiWF4oU5d87dtgqVRyyzLw3b+aN3bB7Y4ptQDZkI1dZYTw49MFMuBK10dZDAxZXOQSHC6yNLD3OY6zVsw7K4xJaWFldQ49UelxxVWnlPEhBr3GszT6uc7jJ4fazf4KFx5p0HFH+Kme9CLle4h6bZFkfxhNouAIoJVqfHQSKbSkDFnVpMhEpovC481NNVcr3SaWlZzTovJErCqgydaMIYBRk+tKfFLC9Wmk75iyqg1DJBWfRxT7pONvTAZom2YC23QsMpOg0B0l0NDh2YjnzjpyvxLrYOK1o3ckLZ5WujSBHr8YL2gxnw85lxEop9c9TynkbMD/kqy+svv/Jb9wu5jh7s+jQbpGzI+ZLu0byEuHZ+wvt6Ays9TJIYl8A5+i0DHHGjvYQ1JLGPuOlaR/TpRFqvXCzHR2BO5iKg0Zmm/ic0W2ZXrB+Gve2uEt1dJKs/QXbwePE)

<details>
<summary>3.5 以前の使用方法</summary>

```vue
<script setup>
import { ref, onMounted } from 'vue'
Expand All @@ -117,7 +170,7 @@ onMounted(() => console.log(itemRefs.value))
</template>
```

[Playground で試す](https://play.vuejs.org/#eNpFjs1qwzAQhF9l0CU2uDZtb8UOlJ576bXqwaQyCGRJyCsTEHr3rGwnOehnd2e+nSQ+vW/XqMSH6JdL0J6wKIr+LK2evQuEhKCmBs5+u2hJ/SNjCm7GiV0naaW9OLsQjOZrKNrq97XBW4P3v/o51qTmHzUtd8k+e0CrqsZwRpIWGI0KVN0N7TqaqNp59JUuEt2SutKXY5elmimZT9/t2Tk1F+z0ZiTFFdBHs738Mxrry+TCIEWhQ9sttRQl0tEsK6U4HEBKW3LkfDA6o3dst3H77rFM5BtTfm/P)
</details>

</div>
<div class="options-api">
Expand Down Expand Up @@ -173,6 +226,26 @@ export default {

<div class="composition-api">

```vue
<script setup>
import { useTemplateRef, onMounted } from 'vue'
import Child from './Child.vue'

const childRef = useTemplateRef('child')

onMounted(() => {
// child.value は <Child /> のインスタンスを保持します。
})
</script>

<template>
<Child ref="child" />
</template>
```

<details>
<summary>3.5 以前の使用方法</summary>

```vue
<script setup>
import { ref, onMounted } from 'vue'
Expand All @@ -190,6 +263,8 @@ onMounted(() => {
</template>
```

</details>

</div>
<div class="options-api">

Expand Down
57 changes: 32 additions & 25 deletions src/guide/typescript/composition-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,17 @@ const foo = inject('foo') as string

## テンプレート参照の型付け {#typing-template-refs}

Vue 3.5 と `@vue/language-tools` 2.1(IDE の言語サービスと `vue-tsc` の両方をサポート)では、SFC の `useTemplateRef()` で作成された `ref` の型は、`ref` 属性が使用されている要素またはコンポーネントに基づいて、静的な `ref` の型を**自動的に推論**できます。

自動推論が不可能な場合でも、ジェネリック引数を使用してテンプレート参照を明示的な型にキャストすることができます:

```ts
const el = useTemplateRef<HTMLInputElement>(null)
```

<details>
<summary>3.5 以前の使用方法</summary>

テンプレート参照は、明示的な型引数と初期値 `null` を指定して作成されます:

```vue
Expand All @@ -389,50 +400,45 @@ onMounted(() => {
</template>
```

</details>

適切な DOM インターフェースを取得するには、[MDN](https://developer.mozilla.org/ja/docs/Web/HTML/Element/input#技術的概要) のようなページを確認してください。

厳密な型安全性のために、`el.value` にアクセスする際には、オプショナルチェーンもしくは型ガードをする必要があります。なぜなら、コンポーネントがマウントされるまでは ref の初期値は `null` であり、参照されていた要素が `v-if` によってアンマウントされた場合にも `null` にセットされる可能性があるからです。

## コンポーネントのテンプレート参照の型付け {#typing-component-template-refs}

時に、子コンポーネントのパブリックメソッドを呼ぶために、子コンポーネントのテンプレート参照に型づけする必要があるかもしれません。例えば、モーダルを開くメソッドを持つ `MyModal` という子コンポーネントがあるとします:

```vue
<!-- MyModal.vue -->
<script setup lang="ts">
import { ref } from 'vue'

const isContentShown = ref(false)
const open = () => (isContentShown.value = true)
Vue 3.5 と `@vue/language-tools` 2.1(IDE の言語サービスと `vue-tsc` の両方をサポート)では、SFC の `useTemplateRef()` で作成された `ref` の型は、`ref` 属性が使用されている要素またはコンポーネントに基づいて、静的な `ref` の型を**自動的に推論**できます。

defineExpose({
open
})
</script>
```
自動推論が不可能な場合(例えば、SFC 以外の使用や動的コンポーネントの場合)でも、ジェネリック引数を使用してテンプレート参照を明示的な型にキャストすることができます。

`MyModal` のインスタンスの型を得るために、まず `typeof` によって型を取得し、次に TypeScript の組み込みユーティリティーの `InstanceType` を使って型を抽出する必要があります:
インポートされたコンポーネントのインスタンスの型を得るために、まず `typeof` によって型を取得し、次に TypeScript の組み込みユーティリティーの `InstanceType` を使って型を抽出する必要があります:

```vue{5}
<!-- App.vue -->
<script setup lang="ts">
import MyModal from './MyModal.vue'
import { useTemplateRef } from 'vue'
import Foo from './Foo.vue'
import Bar from './Bar.vue'

const modal = ref<InstanceType<typeof MyModal> | null>(null)
type FooType = InstanceType<typeof Foo>
type BarType = InstanceType<typeof Bar>

const openModal = () => {
modal.value?.open()
}
const compRef = useTemplateRef<FooType | BarType>('comp')
</script>

<template>
<component :is="Math.random() > 0.5 ? Foo : Bar" ref="comp" />
</template>
```

コンポーネントの正確な型がわからない場合や重要でない場合は、代わりに `ComponentPublicInstance` を使用できます。この場合、`$el` のようなすべてのコンポーネントで共有されているプロパティのみが含まれます:

```ts
import { ref } from 'vue'
import { useTemplateRef } from 'vue'
import type { ComponentPublicInstance } from 'vue'

const child = ref<ComponentPublicInstance | null>(null)
const child = useTemplateRef<ComponentPublicInstance | null>(null)
```

参照されるコンポーネントが[ジェネリックコンポーネント](/guide/typescript/overview.html#generic-components)の場合、例えば `MyGenericModal` の場合:
Expand All @@ -457,15 +463,16 @@ defineExpose({
```vue
<!-- App.vue -->
<script setup lang="ts">
import { useTemplateRef } from 'vue'
import MyGenericModal from './MyGenericModal.vue'
import type { ComponentExposed } from 'vue-component-type-helpers'

import type { ComponentExposed } from 'vue-component-type-helpers';

const modal = ref<ComponentExposed<typeof MyModal> | null>(null)
const modal = useTemplateRef<ComponentExposed<typeof MyGenericModal>>(null)

const openModal = () => {
modal.value?.open('newValue')
}
</script>
```

なお、`@vue/language-tools` 2.1 以降では、静的テンプレート参照の型は自動的に推論されるので、上記はエッジケースでのみ必要となります。