1
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?

Typescriptの余剰プロパティチェックについて

1
Posted at

直接書くとダメなのに変数に入れるとOK

tyoescriptを勉強していたところなんで?にぶつかったので記事にしておきます。
まずは、以下のコードを見てください

type User = { name: string };

//  パターンA:オブジェクトリテラルを直接代入(エラー)
const user1: User = { name: "Taro", age: 25 }; 

//  パターンB:一度変数に受けてから代入(パス)
const obj = { name: "Taro", age: 25 };
const user2: User = obj;

やっていること同じなのになんで片方はOKでもう片方はダメなの?
と思い色々調べたので記事にしておきます。(もったいない精神)

対象者

typescript勉強中で上記がよくわかない人

なんで「直接代入」は厳しいの?

まず結論から回答しますと、タイポ(打ち間違い)対策です。
直接代入の際は、まさに入力中なので厳しくチェックされます。

上記が回答ですが、これだけだとそういうもんかで終わってしまうので、
以下ではTypescriptの設計思想Freshnessという概念について記載します。

「構造的部分型」と「Freshness」

構造的部分型という理論

TypeScriptの型システムは、「構造的部分型(Structural Subtyping)」 という理論に基づいています。

構造的部分型とは オブジェクトが持つプロパティの「名前」と「型」が一致していれば、たとえ余計なプロパティを持っていても互換性があるとみなす方式。

本来のルールに従えば、パターンAも「name を持っているのだから User として認めるべき」です。しかし、あえてここでエラーを出すのが 余剰プロパティチェック(Excess Property Checks) です。

では、なぜ本来のルールを曲げてまで厳しくチェックを行うのでしょうか。

Freshnessという例外

TypeScriptは 「作成されたばかりのオブジェクトリテラル」を "Fresh" である と定義し、特別なルールを適用します。

"Object literals get special treatment and undergo excess property checking when being assigned to other variables..." (オブジェクトリテラルは、他の変数に代入されたり引数として渡されたりする際、特別な扱いを受け、余剰プロパティチェックを受けます)

"TypeScript provides a concept of Freshness (also called strict object literal checking) to make it easier to type check object literals [...]. Freshness is lost when an object literal is assigned to a variable or passed as an argument to a non-literal parameter." (TypeScriptは、オブジェクトリテラルの型チェックを容易にするために Freshness(厳格なオブジェクトリテラルチェック)という概念を提供しています。Freshnessは、リテラルが変数に代入されたり、リテラルでないパラメータに渡された瞬間に失われます。) ——

この "Fresh" な状態にある時のみ、余計なプロパティを一切許さない「余剰プロパティチェック」が発動しているため上記のような処理になっています。

では、なぜこの仕様が必要なのか?

「構造的部分型」という柔軟なルールがあるのに、なぜリテラル(Freshなオブジェクト)の時だけ厳しくするのか。それは、「オプションプロパティ(?)」への誤入力を防ぐためです。

もしこの余剰プロパティチェックがなかったら、以下のようなコードはどうなるでしょうか。

type ButtonProps = {
  label: string;
  color?: string; // オプション(あってもなくても良い)項目
};

// color を colour(イギリス英語)と打ち間違えた!
const props: ButtonProps = { 
  label: "送信", 
  colour: "red" 
};

このとき、もしチェックがないとTypeScriptは次のように解釈してしまいます。

「colour は型にないけど、余計なものがあってもいいルールだから無視しよう」
「color は入ってないけど、オプション項目だから入ってなくてもOKだね」

結果として、「打ち間違い(タイポ)をしているのに、エラーも出ずに『色が反映されない』という不具合」 が発生します。JavaScriptで頻発していそうなこの手のバグを、直接代入時の厳格なモードによって未然に防いでいるということですね。

また、直接代入でなく間接代入時はすでにタイポはないものと判断しているから(Freshでない)と判断されているということだと理解しました。

まとめ

TypeScriptが、直接代入(厳格)と間接代入(寛容)で挙動を分けるのは、開発者の意図を呼んでくれているからだと考えます。

Typescript君
直接書いている時(Fresh): 「今、この型を新規作成しているんだね? タイポがないか厳しくチェックするよ」

変数を通している時(非Fresh): 「既にどこかにあるデータを使い回そうとしているんだね? 最低限の形が合っていれば、余計なものは見逃してあげるよ」

と理解できました!

「リテラルには厳格に、既存データには柔軟に」。 この二段構えのチェックですね。

以上、読んでくれてありがとうございました!参考になれば嬉しいです!

1
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
1
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?