kqito's 技術ブログ

技術やプログラミングについて思ったことを呟くブログ

Typescriptのany型はNG

はじめに

こんにちは。Webフロントエンドを専攻しているkqitoです。

今回はTypescriptのany型についての意見をまとめたいと思います。

Javascriptは動的型付けですが、Typescriptは静的型付け言語でコンパイル時にエラーを列挙したり、強力な補完機能を提供することができます。

Typescriptいいですよね本当。最近は型をしっかり定義しないと不安になってしまう病に落ち行ってしまいました。

anyについて様々な議論

Typescriptには様々な機能がありますが、その中でも特徴的なのがany型です。

最近、Typescriptのanyに関する様々な意見がTwitterで見受けられました。

any = 好ましくない

という認識はほぼ全ての方が感じているように思えました。

というのもTypescriptを採用している以上は型定義しないことにはそのメリットが発揮できませんよね。

私の考え

Anyは基本NG

極論かもしれませんが、私はこの一言に尽きると思っています。

Anyを使用することはTypescriptの長所を生かせていません。

any型を使わないと実装できない仕様じゃん...

って思ってたけど、よくよく考えたらany型使わないで実装できるやん!

ってことないですか?私はごく稀にあります...

any型を使うぐらいならunknown型を使用するべきだと思っていますし(もちろん適材適所による)、

そもそもany型使わないで定義できる場合の方が多いです。

ということで私はAnyはNGと思っています。

any型を使うぐらいなら

タイプセーフなunknownを使おう

any型は型推論をしてくれませんし、どういった型が実行されるのかはコンパイル時にわかりません。

例を示します。

// any
const testAnyType: any = [1, 2, 3];
const result = testAnyType.map((v: number) => v * 2);
console.log(result); // [ 2, 4, 6 ]

上記のコードにおいてtestAnyType変数がたまたま配列だったのでエラーが発生しませんでしたが、any型においてそういった保証はありません。そもそもコンパイル時にエラーさえ吐き出しません。

const testAnyType: any = "not an array";
const result = testAnyType.map((v: number) => v * 2); // エラーを検知できない

また、any型なのでArray.prototype等の補完も一切されません。好ましくありませんね。

ということで、any型よりもunknown型を利用しましょう。というわけです。

// unknown
const testUnknownType: unknown = [1, 2, 3];
const result = testUnknownType.map((v: number) => v * 2); // Object is of type 'unknown'

unknown型を利用することでエラーが発生しました。unknown型には型アサーションすることが必要です。今回はtestUnknownType変数がArray型である保証がないためエラーが発生しました。従って以下のような関数と併用することでタイプセーフなコードを作成することができます。

const testUnknownType: unknown = [1, 2, 3];

const isArrayNumber = (check: unknown): check is Array<number> => {
  return Array.isArray(check) && check.some(v => typeof v === "number");
};

if (isArrayNumber(testUnknownType)) {
  // testUnknownTypeがArray<number>として扱われる
  // 補完機能も適用される
  const result = testUnknownType.map((v: number) => v * 2);
  console.log(result);
}

このようにunknown型を確認することが必須となり、必然的にタイプセーフなコードを記述することができます。

タイプセーフなany型と表現されることが多いようですが、その理由も納得が行きます。

このようにanyを使用する前にunknown型を使用することを検討すると良いと感じました。

この部分に関しては以下の記事が非常に参考になりました。 また、typescriptの適切な使用方法も親切に記述してあるので一度目を通すと良いと感じます。

参考記事

any型を使おうとした時(経験談

Genericの部分

大学の課題で「自由にWebでゲームを作成してください」という課題が出ています。また、ちょうどfluxの復習がしたかったのでReact + Flux + Functional componentでオセロゲームを作成しています。 その際に私が実際に躓いた部分だったのですが、公式fluxサンプルにしたがってActionを作成すると以下のような感じになるかと思います

fluxの公式example

const actions: { [key: string]: (...args: Array<any>) => void } = {
  ADD_STONE: (position: Position): void => {
    OthelloDispatcher.dispatch({
      type: OthelloTypes.ADD_STONE,
      payload: {
        position,
      },
    });
  },
};

型のみを抽出すると

 { [key: string]: (...args: Array<any>) => void }

と最初は定義していました。

引数の型をArray<any>とした理由は今後actionにプロパティが追加された時、引数にどういった型がくるかわからないためでした。

でもanyを使いたくない!!!!

🤔🤔🤔🤔🤔🤔🤔🤔

そうだ。アーキテクチャを見直そう。

結果、それぞれのオブジェクトプロパティを宣言するという形式に変更しました。

まずプロパティのKeyをenumで定義します。 (モジュール分割等は省略)

enum OthelloTypes {
  ADD_STONE = 'ADD_STONE'
}

次にそれに対するValueをtypeで定義します

type OthelloActions = {
  [OthelloTypes.ADD_STONE]: (position: Position) => void;
};

その後、actionの型を以下のように修正しました。

// before
//  { [key: string]: (...args: Array<any>) => void }

// after
 [P in OthelloTypes]: OthelloActions[P]

このように見直し・変更を加えることでany型を使用することなくTypescriptらしいコードを書くことができました。

今後Payloadにposition以外のプロパティが追加されても柔軟に対応することができそうです。

結果としてany型を使用することを避けることができました。このようにアーキテクチャに対する理解・見直しを行う事で大体はany型を避けることができるのでいったん踏み止まって考えることが重要だと感じました。

any型を使う場面

ほぼ極めて使う場面はないと思っていますがあえて例を挙げるならば...

ライブラリを作成する時

様々なnpmライブラリを使用する中でany型の利用が好ましいと思われるライブラリがいくつか見受けられました。個人的に納得したのがRxjsのObserverの周り部分です。Rxの使用上any型になってしまうのでそこは納得がいきました。

型定義がない外部ライブラリをとりあえずで使いたい時

@typesも.d.tsも用意されてない外部ライブラリを試験的に使ってみたい!! って時にはanyを使わざるを得ないのではないのでしょうか

もちろん最終的にコミットをする時には型を定義する必要がありますが...

プロジェクト全体で

Eslintでanyに対するルールを指定

例えば、eslintを採用しているプロジェクトではよくtypescript-eslintが採用されていると思いますが、ignoreRestArgsというGenericの部分に関してanyを許諾するかどうかのオプションが用意されています。

言い換えれば、genericにおけるanyを許諾するかどうかをプロジェクト全体で指定する事ができることを示しています。

こういったEslintやtsconfigの部分からanyに関してルールを決めていくことが開発する上で重要だなと感じました。

typescript-eslint

strictを有効に

strictを有効にする事でTypescriptで陥りやすいな問題に対して以下のような設定をしてくれます。

  • --strictNullChecks
  • --noImplicitAny
  • --noImplicitThis
  • --alwaysStrict

Typescriptを採用するからにはこのstrictを有効にすることをお勧めします。

まとめ

anyについてまとめてみました。

もしanyを使用しなければならない状況になったら

型定義できるけど、単純に見落としている説

unknown型などを利用するべき説

そもそもアーキテクチャが好ましくない説

などを検討することでベストなTypescriptの利用ができるのではないのでしょうか。

もし何か修正点がありましたら報告していただけると幸いです

以上です。