Vue.js で JSDoc により $refs の中身を確認して実装する

# Vue.js で JSDoc により $refs の中身を確認して実装する

親コンポーネントが子コンポーネントに `ref` ディレクティブを指定し、
そのメソッドなどを使用したりしますが中身を想像しながら対応することはおおくあります。

そこで JSDoc との連携により子コンポーネントのもっているモノを確認しようと試みます。

## VeeValidate のドキュメントの記述より

最近 3.0 のリリースされた VeeValidate のドキュメントを読んでいたところ
以下のような記述を目にしました。

[Adding Errors Manually - VeeValidate](https://logaretm.github.io/vee-validate/guide/validation-provider.html#adding-errors-manually)

> Typescript and $refs
> 
> If you are using TypeScript you may face issues with $refs not giving you the correct > > typings, you can solve that by defining them as ValidationProvider instances:

```js
export default class App extends Vue {
  $refs!: {
    provider: InstanceType<typeof ValidationProvider>;
  };
}
```

これは TypeScript での VeeValidate の `Validation Provider` を使用する際の記述例です。
`ref="provider"` を指定したものを親コンポーネント側でメソッドを利用する際に型を明示しています。

## VeeValidate での事例を JSDoc の記述にしてみる

やってみます。

### 親コンポーネントで子コンポーネント使用

```vue:child.vue
<template>
  <div>child</div>
</template>

<script>
import Vue from 'vue'
/**
 * @typedef {Object} Methods
 * @property {{(): boolean}} checkName なまえをちぇっくするめそっど
 */

// eslint-disable-next-line
/** @typedef {import("vue/types/options").ThisTypedComponentOptionsWithRecordProps<Vue, {}, Methods, {}, {}>} Options */
/** @type {Options} */
export const options = {
  methods: {
    checkName() {
      return true
    },
  },
}

export default Vue.extend(options)
</script>
```

子コンポーネントとして `child.vue` を用意しましたが
`checkName` メソッドを親コンポーネント側で使用する形です。

```vue:parent.vue
<template>
  <div>
    parent
    <child ref="child" />
  </div>
</template>

<script>
import Vue from 'vue';

import Child from './child.vue';

export const options = {
  components: {
    Child,
  },
  mounted() {
    console.log(this.$refs.child.checkName());
  },
};

export default Vue.extend(options);
</script>
```

親コンポーネント側で `child.vue` を `components` に指定し `ref="child"` を設定、
`mounted()` にて子コンポーネントのメソッド `checkName()` を使用しています。

![no-jsdoc.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116435/721e3a29-cba6-e188-bb2d-1ebd45129fe3.jpeg)

`any` です。
いつもの光景ですね。

### 親コンポーネントに JSDoc 記述 (Vue.extend 使用)

ここで子コンポーネントの記述のように親コンポーネントにも JSDoc の記述を追加します。

```vue:jsdoc-parent.vue
<template>
  <div>
    parent
    <child ref="child" />
  </div>
</template>

<script>
import Vue from 'vue'

import Child from './child.vue'

/**
 * @typedef {Object} Refs
 * @property {InstanceType<typeof Child>} child
 */

/**
 * @typedef {Object} Data
 */

// eslint-disable-next-line
/** @typedef {import("vue/types/options").ThisTypedComponentOptionsWithRecordProps<Vue & { $refs: Refs }, Data, {}, {}, {}>} Options */
/** @type {Options} */
export const options = {
  components: {
    Child,
  },
  mounted() {
    console.log(this.$refs.child.checkName())
  },
}

export default Vue.extend(options)
</script>
```

記述としては子コンポーネントと同様の構成ですが
ポイントは `@typedef` にて `Refs` を定義し、`ThisTypedComponentOptionsWithRecordProps` の使用箇所にて `Vue` に対し `{ $refs: Refs }` を混ぜ込んでいる点です。

`Refs["child"]` には VeeValidate での記述と同様、
`InstanceType` に `typeof Child` を指定しています。

これを確認してみると・・・

![jsdoc-parent.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116435/4e7ec302-c438-fb50-d0ce-da444da4d168.jpeg)

子コンポーネントで JSDoc で記述した内容が親コンポーネントでも確認することができました。

### 親コンポーネントに JSDoc 記述 (Vue.extend 不使用)

以下は `Vue.extend` を使用しない形式です。

`Vue` をファイル内で `@typedef` で `Refs` との [Intersection Type](https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#intersection-types) とし、
`data` に JSDoc で型指定を行うことでコンポーネントオプション内の `Vue` を `Refs` を含んだものにしております。

`Data` の `@typedef` では `@property` が存在しない定義だと、
`$refs` 配下がまるごと `any` になったのでダミーのプロパティなどを定義しないとだめなようです。

```vue:export-default
<template>
  <div>
    parent
    <Child ref="child" />
  </div>
</template>

<script>
import Child from './child.vue'

/**
 * @typedef {Object} Refs
 * @property {InstanceType<typeof Child>} child
 */

/**
 * @typedef {import('vue').default & { $refs: Refs }} Vue
 */

/**
 * @typedef {Object} Props
 */
/**
 * @typedef {Object} Data
 * @property {string} id
 */

export default {
  components: {
    Child,
  },
  /** @typedef {import("vue/types/options").DataDef<Data, Props, Vue>} DataDef */
  /**
   * @type {DataDef}
   * @return {Data}
   */
  data() {
    return {
      id: '',
    }
  },
  mounted() {
    console.log(this.$refs.child.checkName())
  },
}
</script>
```