0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

TypeScript の as は未定義プロパティをスルーする

Last updated at Posted at 2022-08-06

ことの発端

例えばこんな定義があったとします。

Parameter型
type Parameter = {
  command: "start" | "stop";
  duration: number;
};

こいつを初期化する際、色々と面倒な問題が出てきます。

何か問題でも?

返却値とオブジェクトの初期化 Parameter型 を生成する場合、下記のように <Parameter> とか as Parameter とか書くことが多いと思います。

よく見かけるコード
function generateParameter() {
  return <Parameter>{
    command: "start",
    duration: 100
  };
}
function generateParameter2() {
  return {
    command: "start",
    duration: 100
  } as Parameter;
}

これはこれで、間違いではないです。
command"start""stop" を入れないとエラーになりますし、durationnumber型 を入れないとエラーになります。

間違いではないのですが、「間違いが見つかりにくい」という問題が生じます。

見つかりにくい問題

TypeScript での <Parameter>as Parameter の取り扱いは
「型変換ではなく型チェックと型の上書き」
です。1
これがどういうことかというのは、下記のコードを見ると明らかです。

このコードはエラーになりません
function generateParameter() {
  return <Parameter>{
    command: "start",
    duration: 100,
    debug: true
  };
}
function generateParameter2() {
  return {
    command: "start",
    duration: 100,
    debug: true
  } as Parameter;
}

Parameter型 の初期化に debug という Parameter型 に定義されていないプロパティが登場していますが、これは型チェック的にはエラーになりません。つまり、commandduration が既定の型で初期化されていれば Parameter型 としては成立しているからです。(interfaceの考え方と同じ)

今回の Parameter型 のように単純な型ならまだしも、メタプログラミングで交差型や合併型が複雑になってくると、間違いを見つけるのはかなり困難になります。

上記の例だと、デバッグモードなんて存在していないのに「あれれ?デバッグモードがある筈だから設定してみたけど動いてないな~」的な思い込み系のバグです。
または、Parameter型 がインターネット網を経由して対向サーバへデータを送信した際に、対向サーバでパラメータチェックを厳格にやっていてエラーレスポンスが返却され、「何がエラーなんだ!」と対向サーバ作っている人に文句を言ったらフルボッコされるケースなんかもあります。

ちゃんと書くと

で、ちゃんと書こうとするとこうなります。

ちゃんと#1
function generateParameter() : Parameter {
  return {
    command: "start",
    duration: 100
  };
}
ちゃんと#2
function generateParameter() {
  const p: Parameter = {
    command: "start",
    duration: 100
  };
  return p;
}

上記の例では debug みたいな Parameter型 に含まれないプロパティを指定するとエラーになります。
で、まぁ、TypeScriptの良さは高度な型推論にあると思うので、ちゃんと#1 のように関数定義で返却型を指定することは稀だったりします。(意図的に書く場合もありますが)
そうなると、 ちゃんと#2 みたいなパターンなると思うのですが、ちょっと手間だし、返却値を生成するためだけにローカル変数を定義するのもイマイチだったりします。

解決案

今回作ったもの
function as_<T>(o: T): T { return o; };
使用例
function foo() {
  return as_<Parameter>({
    command: "start",
    duration: 100
  });
}

TypeScriptは C++ のようにジェネリクス(template)の特殊化は行われないため、似たような関数がバカスカ生成されることはありません。
しかし、下記のような問題が考えられます。

  • オブジェクトのコピーが発生
  • 関数呼び出しのコストが発生

まず「オブジェクトのコピー」ですが、オブジェクトの実体がコピーされる訳ではなく、単なるポインタのコピー2なので問題ないでしょう。
次に「関数呼び出しのコスト」はオブジェクト初期化の定石であるコンストラクタ呼び出しと比較すると問題にならないんじゃないかと思います。

まとめ

単なる型チェックのために何もしない関数を作らなければならないので、TypeScriptの型チェックの機能として何かあれば良いのだが。。。

  1. 型アサーションとキャストの違い を参照

  2. Javascriptではポインタという定義は無いのですが、概念として考慮しないと説明できないことが多くあります。(ディープコピーやシャローコピーなんかもそうですし、仮引数で受け取ったオブジェクトの操作と代入行為などもポインタを概念として取り入れないと説明が難しいです)

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?