LoginSignup
76
71

More than 1 year has passed since last update.

TypeScript の気をつけたい機能

Last updated at Posted at 2020-09-23

はじめに

TypeScript には間違った使い方をすると型の恩恵が受けられなくなるものの、正しく使えば便利な機能がいくつかあります。

また特に Vue においては比較的 as などを使う機会が多く心理的ハードルが下がり間違ったケースでも使われてしまうケースが多いように感じます。

そこでどういう使い方が間違っていて、どう使うのが正しいのかをまとめ、正しく TypeScript の機能を使いましょうということでこの記事を書きました。

TypeScript の気をつけたい機能

any

いわずもがな。
あらゆるデータを受け付ける型で、乱用すると TypeScript を使っている意味がなくなってしまうため any であることが正しいと説明できる場合以外は できる限り利用は避けましょう

as : 型アサーション、 Type Assertion

as は正しく型推論できない場合などに 型をコンパイラに教える ための機能です。
ただし 値が変換されるわけではない ため間違った型を設定すると、実際のデータと型が一致しなくなり 実行時エラーとなる場合がある ので気をつけましょう。

Bad

userUser 型であると as を使って コンパイラに教えているため user.profile.gender はコンパイルエラーになりませんが、実際は profile プロパティを持っていないため実行時エラーとなります。

type User = {
  id: number;
  name: string;
  profile: {
    gender: string;
  }
}

const user = {
  id: 1,
  name: "yamagen0915",
} as User;

console.log(user.profile.gender);
// Uncaught TypeError: Cannot read property 'gender' of undefined

Good

どう対処すべきかはケースバイケースですが、

// 型定義に合わせ profile を設定する
const user: User = {
  id: 1,
  name: "yamagen0915",
  profile: { 
    gender: 'male'
  }
}

!. : Non Null Assertion

Nullable でないことをコンパイラに教える 機能です。
(Nullable = null or undefined)
as と同様 Nullable になり得る場所で使ってしまうと 実行時エラーとなる場合がある ので気をつけましょう。

Bad

profile?!. でアクセスしているためコンパイルエラーになりませんが、実際は profile プロパティを持っていないため実行時エラーとなります。

type User = {
  id: number;
  name: string;
  profile?: {
    gender: string;
  }
}

const user: User = {
  id: 1,
  name: "yamagen0915",
};

console.log(user.profile!.gender);
// Uncaught TypeError: Cannot read property 'gender' of undefined

Good

null チェックを行うようにしましょう。

if (user.profile) {
  console.log(user.profile.gender);
}

Vue の具体例

Vue2, Vue3 Composition API において具体的にどのようにそれらの機能を使うか、または使わないべきかを紹介していきます。

methods や関数の引数

関数の引数は型を明示する必要 があります。

Bad

引数に型を明示しないと any となります。

export default Vue.extend({
  methods: {
    // id: any, name: any
    foo(id, name) {}
  }
});

// id: any, name: any
const foo = (id, name) => {}

Good

export default Vue.extend({
  methods: {
    foo(id: number, name: string) {}
  }
});

const foo = (id: number, name: string) {}

Good

関数の引数に渡す関数などはその引数の定義から型推論されます。

// n: number
[1, 2, 3, 4, 5].map(n => n + 1);

Good

デフォルト値が存在する場合はデフォルト値の型から型推論されます。

export default Vue.extend({
  methods: {
    // id: number, name: string
    foo(id = 0, name = '') {}
  }
});

// id: number, name: string
const foo = (id = 0, name = '') => {}

template ref

template ref では as を利用しコンパイラに型を教える必要 があります。

Bad

// <input ref="input">
// input: Vue | Vue[] | Element | Element[]
const input = this.$refs.input

// <custom-component ref="custom"/>
// custom: Vue | Vue[] | Element | Element[]
const custom = this.$refs.custom

Good

取得する値に応じて適切な型を as を使って設定しましょう。
ただし $refs で取得できるデータに変更があってもコンパイルエラーにはならないため気をつけましょう。

ちなみに input <-> HTMLInputElement のような対応は下記の定義通りです。

// <input ref="input">
// input: HTMLInputElement
const input = this.$refs.input as HTMLInputElement

// <custom-component ref="custom"/>
// custom: CustomComponent
const custom = this.$refs.custom as InstanceType<typeof CustomComponent>

Composition API template ref

Coposition API では ref を使って template ref と連携しますが、その ref の型引数に型を指定することができます。
ただしその変数の 宣言時には値を入れることができないため Nullable になります。
都度 null チェックを行うのは面倒なため Non Null Assertion の利用を検討しましょう

Good

Non Null Assertion を利用せず都度 null チェックを行ってももちろん問題ありません。

setup() {
  // input: HTMLInputElement | undefined
  const input = ref<HTMLInputElement>(); 

  // custom: CustomComponent | undefined
  const custom = ref<InstanceType<typeof CustomComponent>>(); 

  // 例えばボタンクリックで input, custom へアクセスする場合
  const click = () => {
    if (input.value) {
      console.log(input.value.value);
    }
    if (custom.value) {
      console.log(custom.value.foo);
    }
  };

  return {
    input,
    custom,
    click,
  };
}

Good

template ref の設定が適切な前提で Non Null Assertion を利用することも検討しましょう。
個人的には template ref の設定が間違っている場合に null チェックを行うと実行時エラーになりませんが、エラーになったほうがよいと思うのでこちらの書き方のほうがよいと思っています。

setup() {
  // input: HTMLInputElement | undefined
  const input = ref<HTMLInputElement>(); 

  // custom: CustomComponent | undefined
  const custom = ref<InstanceType<typeof CustomComponent>>(); 

  // 例えばボタンクリックで input, custom へアクセスする場合
  const click = () => {
    console.log(input.value!.value);
    console.log(custom.value!.foo);
  };

  return {
    input,
    custom,
    click,
  };
}

data()

期待通りに型推論されないこともあるため 変数として切り出したり as を使って型を明示する必要 があります。
最終的にオブジェクトの型が正しく型推論されれば良いので対処方法は様々です。

Bad

[], undefined, null は期待通りに型推論されません。

data() {
  return {
    list: [], // any[]
    id: undefined, // undefined
    name: null, // null
  };
}

Good

各プロパティを変数として切り出し型を明示したり、undefined, null の利用をさけるようにしましょう。

data() {
  const list: string[] = [];
  return {
    list, // string[]
    id: 0, // number
    name: '', // string
  };
}

Good

as を使って型を明示しましょう。
また初期値として undefined, null を使う必要がある場合は | null を忘れずにつけるようにしましょう。

data() {
  return {
    list: [] as string[],  // string[]
    id: undefined as number | undefined, // number | undefined
    name: null as string | null, // string | null
  };
}

Bad

オブジェクトの型を as で明示する場合、型の変更があった場合にエラーとならないため避けたほうが良い場合があります。

type User = {
  id: number;
  name: string;
};

data() {
  return {
    // 仮に gender: string が追加されてもエラーとならず型と値が一致しなくなってしまう。
    user: {
      id: 1,
      name: "yamagen0915",
    } as User,
  };
}

Good

変数として切り出し型を明示することで型に変更があった場合にコンパイルエラーとすることができます。

type User = {
  id: number;
  name: string;
};

data() {
  // 仮に gender: string が追加された場合にエラーとなる
  const user: User = {
    id: 1,
    name: "yamagen0915",
  };

  return {
    user,
  };
}

axios

Vue, TypeScript と直接は関係ありませんが API へのリクエストを axios を利用しているプロジェクトも多いと思うので紹介します。
axios.get 等には レスポンスの型を型引数として指定することができる ため設定しましょう。

Bad

型引数に何も指定しない場合 response.dataany となります。

const response = await axios.get('/api/users');
response.data // any

Good

型引数に API のレスポンスの型を指定します。
ただし API のレスポンスが本当にその型の通りかどうかは保証されないため API の仕様が変わった場合は合わせて型定義も合わせて修正するようにしましょう。

type User = {
  id: number;
  name: string;
};

const response = await axios.get<User[]>('/api/users');
response.data // User[]
76
71
2

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
76
71