【TypeScript】any型とunknown型の違いを具体例で理解する

JavaScript

any型とは

anyは型が不定の時に使う型です。
any型にはどのような値もセットできますし、any型の値を参照する際の制約もありません。
any型には型に対する制約がなにもないため、TypeScriptの型推論の機能を放棄した型といえます。

unknown型とは

unknownもanyと同様、型が不定の時に使う型です。
ただしunknownの場合はanyと異なり、型の制約があります。

unknown型にはどのような値もセットできますが、オブジェクトのプロパティやメソッドを参照する場合は型が特定されている必要があります。

unknown型のオブジェクトのプロパティやメソッドを参照するとコンパイルエラーになるため、unknown型はタイプセーフ(型安全性が保証されている)なany型といえます。

any型とunknown型の挙動について

any型とunknown型の挙動の具体例を紹介します。

どのような値もany型・unknown型にセットできる

any型とunknown型にはどのような値もセットできます。

const any1: any = 1;
const any2: any = 'hoge';
const any3: any = {
  name: "tama",
  meow: "meow!!meow!!"
};
const any4: any = null;
const any5: any = undefined;
const any6: any = true;
const unknown1: unknown = 1;
const unknown2: unknown = 'hoge';
const unknown3: unknown = {
  name: "tama",
  meow: "meow!!meow!!"
};
const unknown4: unknown = null;
const unknown5: unknown = undefined;
const unknown6: unknown = true;

値の参照はany型もunknown型もできる

any型とunknown型にセットした値は参照可能です。

const any1: any = 1;
console.log(any1);
// 1

const any2: any = "hoge";
console.log(any2);
// 'hoge'

const any3: any = {
  name: "tama",
  meow: "meow!!meow!!"
};
console.log(any3);
// {
//   "name": "tama",
//   "meow": "meow!!meow!!"
// }
const unknown1: unknown = 1;
console.log(unknown1);
// 1

const unknown2: unknown = 'hoge';
console.log(unknown2);
// 'hoge'

const unknown3: unknown = {
  name: "tama",
  meow: "meow!!meow!!"
};
console.log(unknown3);
// {
//   "name": "tama",
//   "meow": "meow!!meow!!"
// }

any型にはオブジェクトのプロパティやメソッドの参照に制約はない

any型はオブジェクトのプロパティやメソッドの参照にも制約はありません。

const any1: any = 1;
console.log(any1.toFixed());
// 1

const any2: any = "hoge";
console.log(any2.toUpperCase());
// 'Hoge'

const any3: any = {
  name: "tama",
  meow: "meow!!meow!!"
};
console.log(any3.name);
// "tama"

制約がないためコンパイルエラーにもなりません。
ですので、参照方法に問題がある場合は実行して初めて気がつくことになります。

const any1: any = 1;

// number型に存在しないtoUpperCase()メソッドを利用しているためエラー
console.log(any1.toUpperCase());
// toUpperCase is not a function

const any2: any = "hoge";

// string型に存在しないtoFixed()メソッドを利用しているためエラー
console.log(any2.toFixed());
// toFixed is not a function

const any3: any = {
  name: "tama",
  meow: "meow!!meow!!"
};

// 存在しないプロパティを参照しているためundefined
console.log(any3.title);
// "undefined"

unknown型は型を特定しないとオブジェクトのプロパティやメソッドの参照ができない

unknown型は型の特定をしないとオブジェクトのプロパティやメソッドの参照ができません。
unknown型の値からオブジェクトのプロパティやメソッドを参照するとObject is of type 'unknown'.というコンパイルエラーになります。

const unknown1: unknown = 1;
console.log(unknown1.toFixed()); // コンパイルエラー

const unknown2: unknown = 'hoge';
console.log(unknown2.toUppserCase()); // コンパイルエラー

const unknown3: unknown = {
  name: "tama",
  meow: "meow!!meow!!"
};
console.log(unknown3.title); // コンパイルエラー

unknown型のオブジェクトのプロパティやメソッドを参照する方法

unknown型の値の型を特定し、オブジェクトのプロパティやメソッドを参照する方法について紹介します。

as(型アサーション、Type Assertion)によってunknown型を任意の型で上書きする

型アサーション(Type Assertion)はTypeScriptによって推論された型を上書きする機能です。型アサーションの定義はas 指定したい型です。

型アサーションを利用してunknown型を任意の型に変更することでオブジェクトのプロパティやメソッドを参照できます。

以下はstring型のtoUpperCase()メソッドを型アサーションを利用して参照する例です。

const unknownValue: unknown = "hoge"

// unknownValue.toUpperCase()だとエラーになるが、
// (unknownValue as string).toUpperCase()であれば参照可能
console.log((unknownValue as string).toUpperCase()) // "HOGE"

型アサーションの詳細解説はTypeScriptのas(型アサーション)の概要と使いどころで紹介しています。

プリミティブ型のメソッドを参照する場合、typeofによる型ガード(Type Guard)で型を推定する

プリミティブ型は真偽値や数値など基本的な値の型です。

型ガード(Type Guard)とはif文やcase文をはじめとした条件分岐で変数の型を判別し、ブロック内の変数の型を絞り込む機能のことをいいます。プリミティブ型の型を判定する場合はtypeof演算子を利用します。

型ガードで型を推定することでプリミティブ型のメソッドを参照できます。

以下はstring型のtoUpperCase()メソッドを型ガードを利用して参照する例です。

const unknownValue: unknown = "hoge"
if (typeof unknownValue === 'string') {
  // string型と推定されるので、toString()が実行できる
  console.log(unknownValue.toUpperCase()) // "HOGE"
}

プリミティブ型の詳細解説は【JavaScript】データ型(プリミティブ型とオブジェクト)、リテラルに関する基礎知識で紹介しています。

型ガードの詳細解説は【TypeScript】型ガード(Type Guard)の概要、typeofとinstanceofの利用例で紹介しています。

参考: unknown型のオブジェクトのプロパティを、型安全性が保証された状態で参照する方法

asを利用すればunkown型のオブジェクトのプロパティは参照できます。

type CatType = {
  name: string;
  meow: string;
}

const neko: unknown = {
  name: 'Neko',
  meow: "meow!!meow!!"
}

console.log((neko as CatType).meow) // meow!!meow!!

ただし、型アサーションは任意の型に上書きできるため、型安全性が保証されなくなるので注意が必要です。
つまり、型変換に問題があってもコンパイルエラーとして検知されず、実行時に初めて気が付くことになります。

以下は型の変換対象のオブジェクトと変換後の型の不一致により、意図した結果にならない例です。

type CatType = {
  name: string;
  meow: string;
}

const ahiru: unknown = {
  name: 'Ahiru',
  quack: "quack!!quack!!"
}

// ahiruにはmeowプロパティがないためundefinedになる
console.log((ahiru as CatType).meow) // undefined

オブジェクトの型ガードは、例えばinを利用したプロパティの有無の判定で実装できます。
しかし、unknown型のオブジェクトはプロパティを参照できません。
ですので、unknown型のオブジェクトの型ガードを実装する場合は、プロパティを参照できるようにするためasでオブジェクトの型を変換する必要があります。

unknown型のオブジェクトのプロパティを、型ガードを利用して参照する方法は以下の通りです。

type CatType = {
  name: string;
  meow: string;
}

const neko: unknown = {
  name: 'Neko',
  meow: "meow!!meow!!"
}

const ahiru: unknown = {
  name: 'Ahiru',
  quack: "quack!!quack!!"
}

// プロパティを参照できるようにするため、型変換をする
const cat = neko as CatType

if ('meow' in cat && 'name' in cat) {
  // catは meowとnameプロパティを持つ型(= CatType)と推定される
  console.log(cat.meow)
} else {
  console.log('not a cat.')
}
// "meow!!meow!!"

// プロパティを参照できるようにするため、型変換をする
const duck = ahiru as CatType

if ('meow' in duck && 'name' in duck) {
  console.log(duck.meow)
} else {
  // duckはmeowプロパティを持たないので、else節が実行される
  console.log(`not a cat.`)
}
// not a cat.

上記のコードをユーザー定義型ガードで書き直すと以下のようになります。

type CatType = {
  name: string;
  meow: string;
}

const neko: unknown = {
  name: 'Neko',
  meow: "meow!!meow!!"
}

const ahiru: unknown = {
  name: 'Ahiru',
  quack: "quack!!quack!!"
}

const isCatType = (animal: unknown): animal is CatType => {
  const cat = animal as CatType

  // trueならCatType型と推定される
  return 'meow' in cat && 'name' in cat
}

if (isCatType(neko)) {
  console.log(neko.meow)
} else {
  console.log('not a cat.')
}
// "meow!!meow!!"

if (isCatType(ahiru)) {
  console.log(ahiru.meow)
} else {
  console.log('not a cat.')
}
// not a cat.

inの詳細解説は【TypeScript】inを利用して型の絞り込みを行う方法で紹介しています。

ユーザー定義型ガードの詳細解説はTypeScriptのis(Type Predicate)を使ってユーザー定義型ガードを実装するで紹介しています。

まとめ

any型とunknown型の違いまとめ
  • anyとunknownは型が不定の時に利用する型
  • any型とunknown型にはどのような値もセットできる
  • any型には、オブジェクトのプロパティやメソッドを参照する際の制約はない
  • unknown型のオブジェクトのプロパティやメソッドを参照する際は型を特定する必要がある
  • 型の特定には型アサーションや型ガードを利用する

さいごに

Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!

参考