LoginSignup
7
2

More than 3 years have passed since last update.

Vetur で Vue.extend() 使用時に data の中身を推論するには computed, methods あるいは props 定義が必要っぽい (2018 年 11 月時点)

Last updated at Posted at 2018-11-19

Vue.extend() 使用時の data 推論には computed などの定義が必要っぽい

Type Vue without TypeScript の JSDoc による戻り値などの型指定をやっていたところ $data についてつまづいたので
Visual Studio Code により Vue CLI 3Nuxt.js 2 などで試しました。

Vue.extend() による $data の推論に必要と思われる options は・・・

computedmethods

Vue Forum の投稿の
Vue2.5 でthisが推論されないcomputed のあたりを参考にしました。

data-inferred-by-computed-and-methods
<script>
import Vue from 'vue';
export default Vue.extend({
  data() {
    return {
      name: 'taro',
    };
  },
  // computed は中身が通常の関数、アロー関数どちらでも問題なし
  // ただし定義をしない、あるいは空にすると $data 配下が any 扱いに
  computed: {
    computedName() {
      return this.name
    },
  },
  mounted() {
    // (property) name: string
    this.name;
  },
  // methods は空でも定義があればよく、
  // 中身の定義をする場合でも通常の関数、アロー関数どちらでもよい
  methods: {
    test: () => 1,
    exec() {},
  },
});
</script>

あまりよくない computed

投稿時に下記のような実例としたが Symbol() により computed を定義すると
data 配下の要素をそのまま返すような computed で使用したプロパティ自体が any となる

symbol-function
<script>
import Vue from 'vue';
export default Vue.extend({
  ・・・
  computed: {
    [Symbol()]() {
      return 0;
    },
    // [Symbol()]: () => 0,

    // 以下の事例においては any とはならず定義した型が推論の結果となる
    // aaa: () => 0,
    // [`aaa`]: () => 0,
    computedName() {
      return this.name
    },
  },
  mounted() {
    // computedName() を定義する前までは string として扱われていた
    // any
    this.name
  },
  ・・・
});
</script>

propsdefault 定義にアロー関数が指定されているもの

data-inferred-by-props-arrow-function
<script>
import Vue from 'vue';
export default Vue.extend({
  props: {
    puroppu: {
      type: Boolean,
      default: () => false,
      // アロー関数でないと $data 配下が any になるだけ
      // default() {
      //   return false;
      // },
    },
  },
  data() {
    return {
      name: 'saburo',
    };
  },
  mounted() {
    // (property) ComponentOptions<Vue, DefaultData<Vue>, DefaultMethods<Vue>, DefaultComputed, PropsDefinition<Record<string, any>>, Record<string, any>>.name: string
    this.name;
  },
});
</script>

踏み込んで調べてはいないですが props 配下にて default をアロー関数で指定したものが存在していると
コメントに記載したような (property) ComponentOptions<Vue, DefaultData<Vue>, DefaultMethods<Vue>, DefaultComputed, PropsDefinition<Record<string, any>>, Record<string, any>>.name: string という推論?が行われ
結果 string が取得できるようです。

かなり長い表示がされるので前述の computed methods のほうを使いたいと感じましたが
どうも props 配下に 1 つでも default にアロー関数を使用しているとこちらの方式が優先されるもようです。

computed まわり

十分なプロパティ指定をしないと data 配下をそのまま返すような computed の型は推論されない

十分な指定は したのほうをさんしょう

not-inferred-computed
<script>
import Vue from 'vue';
export default Vue.extend({
  ・・・
  computed: {
    computedName() {
      // メソッド内部では正しく推論されるが
      // (property) name: string
      return this.name
    },
  },
  mounted() {
    // 使用時に推論されない
    // any
    this.computedName
  },
  ・・・
});
</script>

computed を推論させるために

2 種類の方法が考えられる模様です

  • 型が明確な記述の使用
  • Type Vue without TypeScript の事例の通り JSDoc による @returns などの指定

    • @returns での指定
    • 処理中において @type により型の明示された変数を戻り値として指定
inferred-computed
<script>
import Vue from 'vue';
export default Vue.extend({
  ・・・
  computed: {
    /**
     * @returns {string}
     */
    computedName() {
      return this.name
    },
    typedName() {
      /** @type {string} */
      const name = this.name
      return name
    },
    fullName() {
      return `sato ${this.name}`
    },
    // String のメソッドを使用しても推論されない模様
    trimmedName() {
      // (method) String.trim(): string
      return this.name.trim()
    },
  },
  mounted() {
    // (property) computedName: string
    this.computedName
    // (property) typedName: string
    this.typedName
    // (property) fullName: string
    this.fullName
    // (property) trimmedName: any
    this.trimmedName
  },
  ・・・
});
</script>

props の推論

props に指定した要素を推論させる場合
data, computed それぞれ推論可能な状態でさらに props への注釈が必要である模様

  • 直接型を指定する
  • type, default などを指定した構造の場合注釈として constructor などの指定
inferred-props
<script>
import Vue from 'vue';
class Entity {
  id = 1
  name = 'entetei'
  constructor() {}
}
export default Vue.extend({
  ・・・
  props: {
    /*
     * マウスオーバーにより表示される推論の結果での type を @type への指定とする
     * (property) propNum: {
     *     type: NumberConstructor;
     *     default(): number;
     * }
     */
    propNum: {
      type: Number,
      default() {
        return 1
      },
    },
    // type に Number や String などの指定をした場合
    /** @type {NumberConstructor} */
    propId: {
      type: Number,
      default() {
        return 1
      },
    },
    // 直接 Number などを指定した場合
    propNo: Number,
    // 独自に定義した場合の注釈
    /** @type {{ new (): Entity }} */
    propEntity: {
      type: Entity,
      default() {
        return new Entity()
      },
    },
  },
  mounted() {
    // (property) propId: number
    this.propId
    // (property) propNo: number
    this.propNo
    // (property) propEntity: Entity
    this.propEntity
  },
  ・・・
});
</script>

とりあえず推論される記述

props, data, computed のセット

inferred-template

<script>
import Vue from 'vue'
class Entity {
  id = 1
  name = 'entetei'
  constructor() {}
}

export default Vue.extend({
  props: {
    /** @type {NumberConstructor} */
    propId: {
      type: Number,
      default() {
        return 1
      },
    },
    propNo: Number,
    /** @type {{ new (): Entity }} */
    propEntity: {
      type: Entity,
      default() {
        return new Entity()
      },
    },
    // props に関数を指定する場合 default の戻り値でなく、default自体が初期値となるもよう
    /** @type {FunctionConstructor} */
    propFunction: {
      type: Function,
      default() {
        return 1;
      },
    },
  },
  data() {
    return {
      name: 'taro',
    }
  },
  computed: {
    entity: () => Entity,
    /**
     * @returns {string}
     */
    computedName() {
      return this.name
    },
    typedName() {
      /** @type {string} */
      const name = this.name
      return name
    },
    fullName() {
      return `sato ${this.name}`
    },
    trimmedName() {
      return this.name.trim()
    },
  },
  mounted() {
    // (property) propId: number
    this.propId
    // (property) propNo: number
    this.propNo
    // (property) propEntity: Entity
    this.propEntity
    // (property) propFunction: Function
    this.propFunction
    // (property) name: string
    this.name
    // (property) computedName: string
    this.computedName
    // (property) typedName: string
    this.typedName
    // (property) fullName: string
    this.fullName
    // props も含めて定義されていると data の直接の推論がきくもよう
    // (property) trimmedName: string
    this.trimmedName
  },
  methods: {},
})
</script>

不要な実装の記述をしたくない場合の JSDoc 利用

JSDoc 使用であれば props computed, data への JSDoc の記述を行い
methods を用意してあげればよいようです。

props computed, data に具体的な実装が入った場合には
記述を修正する必要が発生しますが。

unknown-options
<script>
import Vue from 'vue';

/**
 * @typedef {Object<string, unknown>} UnknownObject
 */

export default Vue.extend({
  /** @type {UnknownObject} */
  props: {},
  /** @type {{new(): UnknownObject}} */
  data() {
    return {};
  },
  /** @type {UnknownObject} */
  computed: {},
  mounted() {
    // CombinedVueInstance<Vue, new () => {
    //     [x: string]: unknown;
    // }, {}, {
    //     [x: string]: ...;
    // }, Readonly<Record<string, any>>>
    this;
  },
  methods: {},
});
</script>
7
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
2