LoginSignup
18
5

More than 1 year has passed since last update.

【TypeScript】バックエンドをかじっている私は、「部分型」は「継承」と同じ考え方と認識するとしっくりきた

Last updated at Posted at 2022-08-29

1.はじめに

本記事ですが、@kazatsuyu 様よりご指摘とアドバイス修正いたしました。
サンプルコードの例(命名)が良くなかったり、
部分型を上位互換と混乱を招く表現をしているとご丁寧にコメントを頂きました。

今回その修正の記事となります。

@kazatsuyu 様、ありがとうございました。

以前の記事を読んだ下さった方、混乱を招く記事を登校してしまい、申し訳ありませんでした。
こちらの記事も読んでいただけますと幸いです。

まだ、間違って解釈している所ありましたら、ご指摘いただけますと幸いです。


部分型関係は、TypeScript を理解する上で重要な概念とされています。

ただ、

「どっちがどっちの部分?」
「なぜ 〇〇 型が ◇◇ 型の部分型なら、 〇〇 型の値が ◇◇ 型の値を使える?」

となったので、部分型の概要を抑えるために「 書籍:プロを目指す人のためのTypeScript入門 」を参考に記事を書きました。

頭が柔らかくない僕は、理解するのに苦しみました。
というか、間違って解釈していました…

同じ境遇をお持ちの方は見ていただけますと幸いです。

2.目次

1. はじめに
2. 目次
3. この記事でわかること
4. 環境
5. 部分型
 5.1. 部分型とは?
 5.2. SuperType にオプショナルなプロパティを追加する
 5.3. SuperType を SubType に代入してみると
 5.4. Engineer 型にプロパティを追加
 5.5. 少し複雑な部分型
6. おわりに
7. 参考

3.この記事でわかること

  • 部分型の特徴
  • 部分型とは、継承と同じ考え方

4.環境

  • TypeScript: 4.7.4
  • Node.js: 16.15.1

5.部分型

では、本題の 「部分型」は「継承」と同じ考え方 について触れていきます。

5.1.部分型とは?

2つの型の互換性を表す概念です。型 S が 型 T の部分型であるとは、S 型の値が T 型の値でもあることを指します。

書籍:プロを目指す人のためのTypeScript入門 鈴木僚太[著]

著者も書籍に書いていますが、
この言葉を見ただけでは、理解ができませんでした。

この 部分型 について、
下記の部分型サンプルコードのエンジニア(Engineer)とフロントエンドエンジニア(FrontendEngineer)を例にして見ていきたいと思います。

type Engineer = {
    name: string;
    year: number;
}

type FrontendEngineer = {
    name: string;
    year: number;
    frontendSkill: Array<string>;
}

const kakedashiFront: FrontendEngineer = {
    name: 'daishi',
    year: 1,
    frontendSkill: ['JavaScript']
};

const kakedashiEngineer: Engineer = kakedashiFront;

/**
 * 実行結果
 * { name: 'daishi', year: 1, frontendSkill: [ 'JavaScript' ] }
 */
console.log(kakedashiEngineer);

ここでは概要を掴むため、Engineer 型に frontendSkill プロパティなどの スキルを記述していません。
違和感を感じるかもしれませんが、後ほど追加した内容でも説明しますのでご了承ください。
いい例が思い浮かばず申し訳ありません。

前置きはこれぐらいにして、本題に入っていきます。

このコードはコンパイルエラーになりません。
これは、FrontendEngineer 型 の値である kakedashiFrontEngineer 型の特徴を持っているからです。

では、どういうことかそれぞれの型を見ていきます。

type Engineer = {
    name: string;
    year: number;
}

type FrontendEngineer = {
    name: string;
    year: number;
    frontendSkill: Array<string>;
}

Engineer 型は、

  • name プロパティは、string
  • year プロパティは、number

のオブジェクトの型です。

Engineer 型では、nameyear 以外を定義していません。
なので、nameyear 以外が あってもなくても 問題ありません。

だけど、ないかも しれへんプロパティにアクセスするとコンパイルエラーになります。

次に、FrontendEngineer 型は、

  • name プロパティは、string
  • year プロパティは、number
  • frontendSkill プロパティは、string 型の配列

のオブジェクトの型です。

FrontEngineer 型は Engineer 型の条件を満たしています。

この型の 条件を満たしている関係 が、部分型関係となります。

Engineer 型は 上位の型(SuperType)FrontendEngineer 型は 下位の型(SubType) という位置づけになります。
(このように考えるといいよと、@kazatsuyu 様よりアドバイス頂きました。ありがとうございます m(_ _)m )

これは、バックエンドをかじっている私には、継承 の SuperClass、SubClass と同じ考え方なんだなと思い、しっくりきました。

継承とは、SuperClass に全体の性質を、SubClass には固有の性質を定義します。
SuperClass の性質を SubClass に引き継いで、新たに性質を付け足したり上書きしたりができる概念のことです。

なので、

  • 上位の型(SuperType)Enigineer 型の性質(型とプロパティ)を引き継いだ 下位の型(SubType)FrontendEngineer 型は持つ
  • 下位の型(SubType)FrontendEngineer 型に新たにプロパティと型を追加する

という特徴になります。

FrontendEngineer 型は Engineer 型を引き継いでいるから、
kakedashiFront オブジェクトの FrontendEngineer 型を Engineer 型としても問題ありません。

これが部分型関係となります。

5.2.SuperType にオプショナルなプロパティを追加する

部分型とは?では、
Engineer 型なのにエンジニアのスキルがなく違和感を感じたかと思います。

ここでは、Engineer 型にオプショナルなプロパティの frontendSkill を追加してみます。

部分型サンプルコード
type Engineer = {
    name: string;
    year: number;
    frontendSkill?: Array<string>; // ← 追加
}

type FrontendEngineer = {
    name: string;
    year: number;
    frontendSkill: Array<string>;
}

const kakedashiFront: FrontendEngineer = {
    name: 'daishi',
    year: 1,
    frontendSkill: ['JavaScript']
};

const kakedashiEngineer: Engineer = kakedashiFront;

/**
 * 実行結果
 * { name: 'daishi', year: 1, frontendSkill: [ 'JavaScript' ] }
 */
console.log(kakedashiEngineer);

ただ、ここでも、
「エンジニアなのにフロントしか定義しないの?」っていうツッコミ👋 はとりあえず置いておいてください。

これは
実行してもコンパイルエラーになりません。

nameyear 以外が あってもなくても 問題ありません。
だけど、ないかも しれへんプロパティにアクセスするとコンパイルエラーになります。

と記述しましたが、
ここで ないかもしれない プロパティを定義しただけになります。

このように、下位の型(SubType) のFrontendEngineer 型のプロパティを 上位の型(SuperType) の Engineer 型が違う型としてもっていても問題ありません。

5.3.SuperType を SubType に代入してみると

先程は、FrontendEngineer (SubType)型の値を Engineer (SuperType)型に代入しても問題なかったですが、
逆に Engineer (SuperType)型の値を FrontendEngineer (SubType)型に代入するとどうなるか見てみます。

type Engineer = {
    name: string;
    year: number;
    frontendSkill?: Array<string>;
}

type FrontendEngineer = {
    name: string;
    year: number;
    frontendSkill: Array<string>;
}

const kakedashiEngineer: Engineer = {
    name: 'daishi',
    year: 1,
    frontendSkill: ['JavaScript']
};

// SuperType の値を SubType 型に代入
const kakedashiFrontendEngineer: FrontendEngineer = kakedashiEngineer;

これをコンパイルしてみると

Type 'Engineer' is not assignable to type 'FrontendEngineer'.
Property 'frontendSkill' is optional in type 'Engineer' but required in type 'FrontendEngineer'.

翻訳すると

'Engineer' 型は 'FrontendEngineer' 型に割り当てることができません。
'frontendSkill' プロパティ は、'Engineer' 型ではオプションですが、'FrontendEngineer' 型では必須です。

つまり、
frontendSkill プロパティは、

SuperType の値を SubType には代入できないことが解るかと思います。

5.4.Engineer 型にプロパティを追加

Engineer 型に backendSkilプロパティ、 infraSkill プロパティを追加してみます。

ここまで Engineer 型に frontendSkill プロパティしかなく違和感を感じた方もいらっしゃるかと思いますが、
ここで Engineer 型に他のスキルのプロパティも追加してみます。

type Engineer = {
    name: string;
    year: number;
    frontendSkill?: Array<string>;
    backendSkill?: Array<string>; // ← 追加
    infraSkill?: Array<string>;   // ← 追加
}

type FrontendEngineer = {
    name: string;
    year: number;
    frontendSkill: Array<string>;
}

const kakedashiFront: FrontendEngineer = {
    name: 'daishi',
    year: 1,
    frontendSkill: ['JavaScript']
};

const kakedashiEngineer: Engineer = kakedashiFront;

/**
 * 実行結果
 * { name: 'daishi', year: 1, frontendSkill: [ 'JavaScript' ] }
 */
console.log(kakedashiEngineer);

Engineer 型の backendSkill プロパティ、 infraSkill プロパティ共に、frontendSkill プロパティ同様にオプショナルなプロパティとなっています。
つまり、backendSkill プロパティ、 infraSkill プロパティは、あってもなくてもいい型です。

なので、
SuperType の Engineer 型を引き継いだ SubType のFrontendEngineer 型に、backendSkill プロパティ、 infraSkill プロパティが存在しなくてもエラーとなりません。

5.5.少し複雑な部分型

少し応用編として、
今まで見てきた Engineer 型と FrontendEngineer 型の部分型の関係を使って、
EngineerTeam 型と FrontendEngineerTeam 型というチームの型を見てみます。

EngineerTeam 型は、FrontendEngineerTeam 型の部分型の関係になります。

複雑な部分型サンプルコード
type Engineer = {
    name: string;
    year: number;
    frontendSkill?: Array<string>;
}

type FrontendEngineer = {
    name: string;
    year: number;
    frontendSkill: Array<string>;
}

type EngineerTeam = {
    teamName: string;
    director: Engineer;
    chief: Engineer;
    employee: Engineer;
}

type FrontendEngineerTeam = {
    teamName: string;
    director: FrontendEngineer;
    chief: FrontendEngineer;
    employee: FrontendEngineer;
}

const bucho: FrontendEngineer = {
    name: 'ぶちょう',
    year: 20,
    frontendSkill: ['TypeScript', 'React', 'Vue'],
};

const katyo: FrontendEngineer = {
    name: 'かちょう',
    year: 10,
    frontendSkill: ['TypeScript', 'React'],
};

const kakedashi: FrontendEngineer = {
    name: 'かけだし',
    year: 1,
    frontendSkill: ['JavaScript'],
};

const frontendTeam: FrontendEngineerTeam = {
    teamName: 'frontendTeam',
    director: bucho,
    chief: katyo,
    employee: kakedashi,
};

const engineerTeam: EngineerTeam = frontendTeam;

/**
 * 実行結果
 * {
 *   teamName: 'frontendTeam',
 *   director: {
 *     name: 'ぶちょう',
 *     year: 20,
 *     frontendSkill: [ 'TypeScript', 'React', 'Vue' ]
 *   },
 *   chief: {
 *     name: 'かちょう',
 *     year: 10,
 *     frontendSkill: [ 'TypeScript', 'React' ]
 *   },
 *   employee: {
 *     name: 'かけだし',
 *     year: 1,
 *     frontendSkill: [ 'JavaScript' ]
 *   }
 * }
 */
console.log(engineerTeam);


今まで同様に
const engineerTeam: EngineerTeam = frontendTeam;
はコンパイルエラーとなりません。

FrontendEngineerTeam 型が EngineerTeam 型を引き継ついでいるかを見てみましょう。

type EngineerTeam = {
    teamName: string;
    director: Engineer;
    chief: Engineer;
    employee: Engineer;
}

type FrontendEngineerTeam = {
    teamName: string;
    director: FrontendEngineer;
    chief: FrontendEngineer;
    employee: FrontendEngineer;
}

directorchiefemployee の各プロパティ以外は同じなので、これらが引き継いでいる関係を示せれば、
FrontendEngineerTeam 型は、EngineerTeam 型を引き継いでいることが言えそうです。

directorchiefemployee の各プロパティは、

  • EngineerTeam 型は、それぞれ Engineer
  • FrontendEngineerTeam 型は、それぞれ FrontendEngineer

です。

FrontendEngineer 型は、Engineer 型を引き継いでいる(部分型)の関係でした。

ということは、
FrontendEngineerTeam 型の directorchiefemployee の各プロパティは、
EngineerTeam 型の directorchiefemployee の各プロパティ型を引き継いでいることが言えますね。

したがって、
FrontendEngineerTeam 型は、EngineerTeam 型を引き継いでいる(部分型)となります。

6.おわりに

部分型の理解に苦しみましたが、継承と同じような特徴なんだなと認識すると部分型掴むことができました。

ご指摘頂いた @kazatsuyu 様ありがとうございました。

部分型の概念を理解できたので、TypeScript の世界がもっと広がればいいなと思います。

併せて他の記事も読んでいただけると嬉しいです🙇‍♂️

最後まで読んでいただきありがとうございました。

7.参考

書籍:プロを目指す人のためのTypeScript入門 鈴木僚太[著] @uhyo

18
5
4

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
18
5