構文

【TypeScript】typeofとkeyofの違いと使い方

更新日:2022/11/07

TypeScriptにはtypeofという演算子があります。
しかしJavaScriptにもtypeofという演算子があります。
何が違うのでしょうか。

また似たような演算子にkeyofがあります。
これも何が違うのでしょうか。

 

typeof演算子

typeof演算子は、元々はECMAScript(JavaScriptの仕様)の演算子です。
しかし、TypeScriptでも異なる機能として実装されています。

プログラムコード上では両方のtypeof演算子が混在します。
機能の違いを把握しておく必要がありますね。

JavaScriptのtypeof演算子

最初にJavaScriptのtypeof演算子について、見ていきます。

JavaScriptでの仕様

JavaScriptのtypeof演算子は、データの型名を動的に返します。


const a = 123;
const b = "abc";
console.log( typeof a ); // "number"
console.log( typeof b ); // "string"

次のように、変数の型をチェックするために使用することが多いです。

if( typeof a === "string"){
  // aを文字列として処理
}

データの型と返す文字列の関係は、次の表のようになっています。

値の型返す値
Undefined"undefined"
Null"object"
Boolean"boolean"
Number"number"
String"string"
Symbol"symbol"
BigInt"bigint"
関数以外のObject"object"
関数Object"function"

JavaScriptのtypeof演算子については、こちらの記事を参考にしてみてください。
【JavaScript】 typeof演算子は何を返しているのか?

TypeScriptでの追加仕様 | 型ガード

JavaScriptのtypeof演算子は、TypeScriptでは型ガードという意味を持っています。

次のコードは、エラーが出力されます。

function func( h:string|number){
    h.toUpperCae(); // エラー: Property 'toUpperCae' does not exist on type 'string | number'.
                           //           Property 'toUpperCase' does not exist on type 'number'.
}

toUpperCae()はStringのメソッドなのでstring型の変数から呼び出される必要があります。

しかし、引数hは string | numberのUnion型です。
string型ではないので、エラーになります。
次に、Union型を展開して型チェックしています。
string型はOKでしたが、number型でエラーになったようです。

トランスパイル後に実行したとき、number型の値を受け取ったらエラーになるので、プログラムコードに問題がありますね。

そこで、typeof演算子で型チェックをおこないます。

function func( h:string|number){
    if( typeof h === "string"){
        return h.toUpperCase();
    }else{
        return h.toString();
    }
}

これで、実行時のエラーが回避できます。

そして、TypeScriptでもコードを追跡して、型を絞り込んでいます。
そのため、エラー表示されません。

このような型を絞り込む処理をTypeScriptでは型ガードと呼んでいます。

TypeScriptのtypeof演算子

TypeScriptのtypeof演算子は、TypeScript固有のコード(型コンテキスト)で使用されます。
型アノテーションや型エイリアスなどですね。
これらは、トランスパイル後に生成されるコードから削除されます。

この点で、JavaScriptとTypeScriptのtypeof演算子は、明確に区別できますね。

TypeScriptでの仕様

TypeScriptのtypeof演算子は、変数やプロパティの型を返します。

let a = 100;
let b: typeof a;  // b は number型
type c = typeof a;  // c は number型

変数やプロパティ以外の値を指定すると、エラーになります。

let b: typeof 100; // エラー: Identifier expected.

"Identifier expected"は「識別子が必要です」という意味です。

次のようにオブジェクトに対してtypeofを使用すると、同じ型の変数を簡便に生成できます。

let a = {
    value:100,
    text:"abc"
};  // aの型は、{ value: number; text: string; }

const b: typeof a ={value:200,text:"hello"};
b.value = 300;
b.flg = true; // エラー:Property 'flg' does not exist on type '{ value: number; text: string; }'.

typeof aで、{ value: number; text: string; } という型が取得され、変数bに適用されています。
つまり変数bを定義している行は、次のコードと同じです。

const b:{ value: number; text: string; } ={value:200,text:"hello"};

プロパティ名をunionで取得

オブジェクトのプロパティを、配列形式でtypeof演算すると、そのプロパティの型が取得されます。

let obj = {
    value:100,
    text:"abc",
    flg:true
};
type t = typeof obj["value"]; // t は number型

これは、typeof obj.value でも同じ結果になります。
感覚的にも当然の結果ですね。

配列の添え字部分をUnion型にすると、typeofの結果をUnion型で返してくれます。

type t = typeof obj["value" | "flg"]; // tは、number | boolean

次のように、個別にtypeof演算したものと同じ結果です。

type t = typeof obj.value | typeof obj.flg;

ただし、同じ型は一回のみUnion型に含まれます。
そのため対象が全て同じ型なら、Union型になりません。

let obj = {
    value:100,
    text:"abc",
    flg:100
};
type t = typeof obj["value" | "flg"]; // tは、number型

全てのプロパティをUnion型にしたいときは、全てのプロパティを指定します。

type t = typeof obj["value" | "text" | "flg"  ]; // tは、number | string | boolean

汎用性が無いですね…
次のように、keyofと組み合わせると、全てのプロパティの型をUnion型で取得できます。

type t = typeof obj[keyof typeof obj]; // tは、number | string | boolean

配列要素の型をunionで取得

配列要素の型をUnion型でする場合、添え字に number を指定します。

const array =[1,"a",true];

type t = typeof array[number];  // tは、string | number | boolean

numberは、配列の全てのインデックスが対象になります。
この特性を利用すると、次のようにオブジェクトの配列に適用できます。

const array =[
    {value:100},{value:"a"},{value:true}
];
type t = typeof array[number]["value"]; // tは、string | number | boolean

 

keyof演算子

keyof演算子はTypeScript固有の演算子です。
JavaScriptには存在しません。

keyof演算子の使い方

keyofはオブジェクトの型に使用する演算子です。
使用すると、プロパティ名を列挙してUnion型に変換したものを返します。

type t = keyof { value: number; text: string; 1:number}; // t は、"value" | "text" | 1

const a:t = 1;

typeofのように、変数を指定するとエラーです。

let a = {
    value:100,
    text:"abc"
};
type t = keyof a; // エラー: 'a' refers to a value, but is being used as a type here. Did you mean 'typeof a'?

この場合は、次のように typeof と組み合わせることができます。

type t = keyof typeof a;

組み合わせについては、次の記事で解説しています。
【TypeScript】オブジェクトや配列のキーや値をUnion型に変換する

数値と数値文字列プロパティの扱い

keyofは、数値と数値文字列を区別します。
上記のコードで、文字列の1("1")を代入するとエラーです。

const a:keyof t = 1; // エラー: Type '"1"' is not assignable to type 'keyof t'.

文字列で定義すると、エラーになりません。

type t =  { value: number; text: string; "1":number};
const a:keyof t = "1";

JavaScriptの仕様上は、数値のプロパティ名は内部的には文字列で登録されています。
つまり、1 と "1" は同じプロパティです。
JavaScriptに詳しい人ほど、混乱しそうです。

keyofの連結

keyofは、"|" で複数連結できます。

type t1 =  { value: number; text: string;};
type t2 =  { value2: number; text2: string;};

type t3 = keyof t1 | keyof t2;  // "value" | "text" | "value2" | "text2"
const a:t3 = "value2";

次のように、typeofを使ってオブジェクト名のマージもできます。

let obj = {
    value2:100,
    text2:"abc"
};
type t1 =  { value: number; text: string;};

type t3 = keyof t1 | keyof typeof obj;  // "value" | "text" | "value2" | "text2"
const a:t3 = "value2";

 

typeof演算子とkeyof演算子の違い

TypeScriptのtypeof演算子とkeyof演算子の違いをまとめておきます。

目的が違う

当たり前ですが、目的が違います。

演算子目的
typeof変数やプロパティの型を取得する
keyofオブジェクトのプロパティ名からUnion型を生成する

対象が違う

演算子の対象も違います。

演算子対象
typeof変数やプロパティ
const hensu = { value: 1, text: "a"};
let value:typeof hensu.value;
value = 100;
keyof型コンテキスト
type t =  { value: number; text: string;};
const a:keyof t = "value";

更新日:2022/11/07

書いた人(管理人):けーちゃん

スポンサーリンク

記事の内容について

null

こんにちはけーちゃんです。
説明するのって難しいですね。

「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。

裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。

掲載コードについては事前に動作確認をしていますが、貼り付け後に体裁を整えるなどをした結果動作しないものになっていることがあります。
生暖かい視線でスルーするか、ご指摘ください。

ご意見、ご指摘はこちら。
https://note.affi-sapo-sv.com/info.php

 

このサイトは、リンクフリーです。大歓迎です。