1.はじめに
過去に、関数型の部分型関係の特性の記事を書いてきました。
本記事では、メソッド記法が部分型の特性を緩和しているため、できるだけ 使用しないほうがいい 理由についてまとめました。
@uhyo さんの「プロを目指す人のための TypeScript 入門」を元にキャッチアップしました。
間違って解釈している所ありましたら、ご指摘いただけますと幸いです。
2.目次
1.はじめに
2.目次
3.この記事でわかること
4.環境
5.メソッド記法の関数は部分型関係を緩和する理由
5.1.通常の関数型とメソッド記法の違い
5.2.メソッド記法での関数宣言が部分型関係を緩和する理由
6.おわりに
7.参考
3.この記事でわかること
メソッド記法で宣言した関数は、
引数に SubType を受け取る関数を引数に SuperType を受け取る関数の代わりに扱うことはできない。
という部分型関係の特性を緩和してしまいます。
下記のコードを用いて説明していきます。
type Engineer = {
name: string;
year: number;
};
type FrontendEngineer = {
name: string;
year: number;
frontendSkill: string;
};
type SelfIntroduction = {
func: (engineer: Engineer) => string;
method(engineer: Engineer): string;
}
const selfIntroductionEngineer: SelfIntroduction = {
func: engineer => `名前は${engineer.name}です。実務経験は${engineer.year}です。`,
method: engineer => `名前は${engineer.name}です。実務経験は${engineer.year}です。`,
};
const doSelfIntroduction = (frontendEngineer: FrontendEngineer) => `
名前は${frontendEngineer.name}です。
実務経験は${frontendEngineer.year}です。
${frontendEngineer.frontendSkill}ができます。
`;
// エラー: Type '(frontendEngineer: FrontendEngineer) => string' is not assignable to type '(engineer: Engineer) => string'.
selfIntroductionEngineer.func = doSelfIntroduction;
// これはエラーが発生しない
selfIntroductionEngineer.method = doSelfIntroduction;
4.環境
- TypeScript: 4.7.4
- Node.js: 16.15.1
5.メソッド記法の関数は部分型関係を緩和する
ここから、「なぜメソッド記法での関数宣言が部分型関係を緩和するのか?」を見ていきますが、
まず、通常の関数型とメソッド記法の関数宣言について簡単にさらっておきたいと思います。
5.1.通常の関数型とメソッド記法の違い
メソッド記法は、オブジェクトリテラル ({ プロパティ: 式 }
のこと)の中で関数を作成するための記法です。
以前の記事にしていますので、詳細はこちらをごらん頂ければです。
今回は、 SelfIntroduction
というオブジェクト型で通常の関数とメソッド記法での関数の型定義をしています。
type SelfIntroduction = {
func: (engineer: Engineer) => string;
method(engineer: Engineer): string;
}
-
func
は、通常の関数型で宣言 -
method
は、メソッド記法で宣言
オブジェクト型の中で関数型を表現する場合、
- 通常の関数型:
関数名: (引数リスト) => 返り値の型
- メソッド記法:
メソッド名(引数リスト): 返り値の型
となります。
通常の関数とメソッド記法の違いがわかったところで、
本題の「メソッド記法での関数宣言が部分型関係を緩和するのか」を見ていきます。
5.2.メソッド記法での関数宣言が部分型関係を緩和する理由
では、本題の「メソッド記法での関数宣言が部分型関係を緩和する理由」について見ていきます。
冒頭にも書きましたが、次のソースコードを用いて見ていきたいと思います。
// SuperType のオブジェクトの定義 ...①
type Engineer = {
name: string;
year: number;
};
// SubType のオブジェクトの定義 ...②
type FrontendEngineer = {
name: string;
year: number;
frontendSkill: string;
};
// 引数で SupetType の通常の関数とメソッド記法の関数をもったオブジェクトの型定義 ... ③
type SelfIntroduction = {
func: (engineer: Engineer) => string;
method(engineer: Engineer): string;
}
// ③ の型のオブジェクトを作成 ...④
const selfIntroductionEngineer: SelfIntroduction = {
func: engineer => `名前は${engineer.name}です。実務経験は${engineer.year}です。`,
method: engineer => `名前は${engineer.name}です。実務経験は${engineer.year}です。`,
};
// 引数で②の SubType を受け取る関数 ...㋐
const doSelfIntroduction = (frontendEngineer: FrontendEngineer) => `
名前は${frontendEngineer.name}です。
実務経験は${frontendEngineer.year}です。
${frontendEngineer.frontendSkill}ができます。
`;
// ④のオブジェクトの通常の関数型で宣言した関数に㋐の関数を代入 ... ㋑
// エラー: Type '(frontendEngineer: FrontendEngineer) => string' is not assignable to type '(engineer: Engineer) => string'.
selfIntroductionEngineer.func = doSelfIntroduction;
// ④のオブジェクトのメソッド記法で宣言した関数に㋐の関数を代入 ...㋒
// これはエラーが発生しない
selfIntroductionEngineer.method = doSelfIntroduction;
ここでは、3 つの関数が存在します。
-
関数
doSelfIntroduction
(㋐)
(frontendEngineer: FrontendEngineer) => string
型 -
関数
selfIntroductionEngineer.func
(通常の関数型)(㋑)
(engineer: Engineer) => string
型 -
関数
selfIntroductionEngineer.method
(メソッド記法の関数)(㋒)
(engineer: Engineer) => string
型
整理すると、
- ㋐は、引数で
FrontendEngineer
型(②のSubType)を受け取り、string
型を返す関数 - ㋑㋒のどちらも、引数で
Engineer
型(①のSuperType)を受け取りstring
型を返す関数
となっています。
そして、
関数 selfIntroductionEngineer.func
(㋑)と関数 selfIntroductionEngineer.method
(㋒)に関数 doSelfIntroduction
(㋐)を代入しています。
// ④ オブジェクトの通常の関数型で宣言した関数に㋐の関数を代入 ... ㋑
selfIntroductionEngineer.func = doSelfIntroduction;
// ④ オブジェクトのメソッド記法で宣言した関数に㋐の関数を代入 ...㋒
selfIntroductionEngineer.method = doSelfIntroduction;
しかし、これらは、 引数の型による部分型関係 を違反しています。
詳細は、【TypeScript】関数型の引数の型による部分型関係ややこしすぎません?なので、初心者にも理解できるように噛み砕いてみた の記事に書いていますが、
ざっくりいうと、
OK
引数に SuperType を受け取る関数を、引数に SubType を受け取る関数の代わりに扱うことができる。
NG
引数に SubType を受け取る関数を、引数に SuperType を受け取る関数の代わりに扱うことはできない。
というのが部分型関係の特性となっています。
今回は、NG である 「引数で SubType を受け取る関数(㋐)を、引数で SuperType を受け取る関数(㋑㋒)の代わりに扱おう」としています。
なので、コンパイルエラーが生じるはずです。
では、SubType の引数を受け取る関数 doSelfIntroduction
(㋐)を、代入した各関数を見てみましょう。
通常の関数型で宣言されたの関数 selfIntroductionEngineer.func
(㋑)はエラーとなっていますね。
// ④ オブジェクトの通常の関数型で宣言した関数に㋐の関数を代入 ... ㋑
// エラー: Type '(frontendEngineer: FrontendEngineer) => string' is not assignable to type '(engineer: Engineer) => string'.
selfIntroductionEngineer.func = doSelfIntroduction;
しかし、メソッド記法で宣言されたの関数 selfIntroductionEngineer.method
(㋒)は エラーとなっていません 。
// ④ オブジェクトのメソッド記法で宣言した関数に㋐の関数を代入 ...㋒
// これはエラーが発生しない
selfIntroductionEngineer.method = doSelfIntroduction;
これは、
「引数で SubType を受け取る関数を引数で SuperType を受け取る関数の代わりに扱うことはできない」
という部分型関係の特性を緩和しています。
型の安全性を破壊しちゃっていますね…
つまり、
「メソッド記法で宣言された関数 は部分型関係の性質を 緩和 している」
ということになります。
6.おわりに
メソッド記法は、部分型関係の特性を緩和している理由がわかったかと思います。
これからは、オブジェクトリテラル内で関数を宣言するときは、
メソッド記法を使わずに通常の関数型で宣言するように心がけたいと思います。
やっと、部分型関係についてわかってきたけど、やっぱり難しいですね…
併せて他の記事も読んでいただけると嬉しいです🙇♂️
最後まで読んでいただき、ありがとうございました😊