Help us understand the problem. What is going on with this article?

TypeScriptの型の基本的な考え方

社内勉強会の資料。

TypeScriptの型の考え方

TypeScriptの型は、「構造的部分型(Structural subtyping)」と呼ばれます。
Rubyなどの宣言的な型のない言語を書く人であれば、ダックタイピングだと言えば通じやすいと思います。

JS的に言えば、「とある型Aにとって必要なプロパティを所持しているオブジェクトは、その型Aであるとみなす」です。

type A = {
  hoge: string;
  piyo: number;
};

{ hoge: "" } // Aでない: 必要なプロパティ`piyo`が存在していない
{ hoge: "", piyo: 123 } // Aである: 必要なプロパティが両方ある
{ hoge: "", piyo: 123, fuga: "" } // Aである: 必要なプロパティが両方ある

これだけ。

ただし、これを使いやすくする、またプログラマのミスを事前に防ぐという目的で、いくつかの例外が存在しています。
TypeScriptの型の基本は構造的部分型ですが、より詳しく知るためにはこの構造的部分型の例外がいつどんな時に発生するのかを知る必要があります。

例外1: 余分なプロパティチェック(Excess Property Checking)

仕組み

https://www.typescriptlang.org/docs/handbook/interfaces.html#excess-property-checks

type A = {
  hoge: string;
  piyo: number;
};

let o1: A;

const o2 = { hoge: "", piyo: 123, foo: "foo" };

o1 = o2; // OK
o1 = { hoge: "", piyo: 123, foo: "foo" }; // Error: Object literal may only specify known properties, and 'foo' does not exist in type 'A'.ts(2322)

さて、o1Aの型エイリアスを当てていて、o2は型の宣言はなしなので、オブジェクトのリテラル型です。

o1 = o2;がOKなのは、構造的部分型であることを考えれば違和感ないと思います。
しかし、最終行は以下のエラーが出てコンパイルが通りません。

Object literal may only specify known properties, and 'foo' does not exist in type 'A'.ts(2322)

構造的部分型であるにも関わらず、余分なプロパティを持っていることを怒られています。
第一の例外はこの「余分なプロパティチェック(Excess Property Checking)」で、エラーの通り、リテラルの代入時は型チェックが厳しくなります。

これは、ライブラリのオプションなどを指定する場合に、タイポなどで存在しないオプションを渡してしまっている場合にすぐに気づけるための仕組みです。

type Option = {
  hogeable?: boolean;
  piyoName: string;
};

type createHoge = (option: Option) => Hoge;

のような型定義のライブラリがあったとして

const hoge = createHoge({ piyoName: "PIYO" });

これは通ります。hogeableはオプション(?付き)なので、Optionに必要な型はpiyoNameだけだからです。

しかし、

// hogeable を hogableにタイポしている!
const hoge = createHoge({ hogable: true, piyoName: "PIYO" });

構造的部分型であれば、必要な型さえあれば他にどんなプロパティを持っていたとしても問題ないはずですが、これは上述の通りエラーになります。

Object literal may only specify known properties, but 'hogable' does not exist in type 'Option'. Did you mean to write 'hogeable'?ts(2322)

例外2: 弱い型(Weak Type)

仕組み

https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-4.html

Weak TypeはTypeScript2.4で導入されました。

Weak Typeとは、全てのプロパティがオプションであるオブジェクト型のことを指します。

type A = {
  hoge?: string;
  piyo?: number;
};

さて、構造的部分型で考えるのであれば、この型には必要なプロパティが存在しないので、全てのオブジェクトが代入可能なはずです。
ちなみに、ここで言う「全てのオブジェクト」 というのは、オブジェクトではないnumberリテラルやstringリテラルなども含みます。

これらのリテラルにはAuto Boxingが効く("hoge".lengthができる理由)ので、オブジェクトとして扱われます。
つまり、{}型がnullundefined以外の値全てを代入可能なように、上記のtyep Anullundefined以外の全ての値を代入可能ということです。

オブジェクトの型を定義しているのにnullundefined以外なんでも入るって、型の定義として意味がなさすぎるので、それを弾く仕組みとしてWeak Typeが導入されました。

Weak Typeにおいては、必ずどれか一つのプロパティを持っているオブジェクト(typeof"object"が返ってくる値)しか代入できません。
さらに、このルールはリテラルであっても変数であっても適用されます。

試してみましょう。
まずはWeak Typeが存在していない世界線の話。
Weak Typeは2.4.0で導入されているので、今回はWeak Typeが存在しないTypeScript2.0.0を使用します。

$ npm install --save-dev typescript@2.0.0
$ npx tsc -v
Version 2.0.0
index.ts
type A = {
  hoge?: string;
  piyo?: string;
};

const val1 = { fuga: 123 };
const val2 = 123;
const val3 = () => {};

let result: A;

result = val1;
result = val2;
result = val3;

こんなコードを用意しました。
type Aはさっきの通り、そしてA型のresultに対して色々代入します。
これをコンパイルすると...

$ ls -l -1
index.ts
node_modules
package-lock.json
package.json

# コンパイル実行
$ npx tsc index.ts
# 問題なく通ってる
$ ls -l -1
index.js # <- 追加されてる
index.ts
node_modules
package-lock.json
package.json

あんれまあ。
普通に通ってしまいます。
まさに構造的部分型なわけですが、オブジェクトの型を定義してるのにnumberリテラルとかオブジェクト以外のもの入れて欲しくないですよね

\そこでWeak Typeの登場です/

次は、TypeScript2.6.1で試します。
(2.4で導入されているのになんで2.6なんやって感じですが、ちょくちょくWeak Type自体にアップデートがかかっていて、2.4.1で試したら普通にリテラル代入できちゃいました😇例にならないので関数も弾いてくれる2.6.1で)

$ npm uninstall typepscript
$ npm install --save-dev TypeScript@2.6.1
$ npx tsc -v
Version 2.6.1
$ npx tsc index.ts
index.ts(12,1): error TS2559: Type '{ fuga: number; }' has no properties in common with type 'A'.
index.ts(13,1): error TS2559: Type '123' has no properties in common with type 'A'.
index.ts(14,1): error TS2559: Type '() => void' has no properties in common with type 'A'.

ということで、Aに対しての代入がこのようにエラーになります。

通すためには、

index.ts
type A = {
  hoge?: string;
  piyo?: string;
};

// hogeを含んでいる
const val1 = { hoge: "", fuga: 123 };

let result: A;

result = val1;

このように一つ以上のプロパティを持っている必要があります。
リテラルの代入はもちろんのこと、Weak Typeの型チェック時には変数に代入していても上記のようにエラーになります。
理由を知っていれば納得できますね。

Excess Property CheckingとWeak Type、挙動似てない?

ちょろっと説明読んだだけだと、この二つの違いは何...ってなるんですが、導入された理由を知っておくと理解が進むと思います。
ていうか、公式のExcess Property Checkingの例のコードでWeak Typeになる型定義をしているのがいけない気がする(と思ってこの例ではExcess Property Checkingの例のコードで非Weak Typeな型定義をしてる)...。

TODO

  • Weak Typeのエラーの他の回避の方法について
ryokkkke
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした