はじめに
- 前回 はちょっと詰め込みすぎたので、その振り返りともういくつかの重要な型についての話をします
- サンプルコードは基本的にはTypescript Playgroundで実行しています。(オプションはデフォルト)
前回のおさらい
-
Typescriptでどのように型が定義されるか
-
Typescriptの基礎型
-
any
とunknown
-
any
はTypescriptで避けるべきものの筆頭です。コンパイルエラーを通すためだけにany
を使わないように
-
- 空を表す基礎型:
null
とundefined
- 無を表す基礎型:
void
とnever
- オブジェクト型
- その他のJavascriptと共通の型
-
-
Typescript特有2の型表現
補足1: オブジェクト型の省略可能メンバ
- オブジェクトを定義するとき、必ずしも必要ではない(あったりなかったりする)メンバがある場合、
?
でそれを表現できます。 - 代入されなかったオブジェクトは
undefined
となります(null
ではないことに注意)
type Person = {
firstName: string,
middleName?: string,
familyName: string
}
let japanese:Person = {firstName: "Taro", familyName: "Yamada"}
console.log(japanese.middleName) //undefined
補足2: 列挙型(enum
)
- JavaやC#でおなじみの列挙型もあります
- Javaほどの表現力はなく、C#っぽい感じ
- 特に何もしなければ、暗黙的に
number
型となる
enum Suit {
Heart,
Club,
Diamond,
Spade
}
let cardOfHeart: Suit = Suit.Heart
-
string
型の指定も可能
enum SuitString {
Heart = "Heart",
Club = "Club",
Diamond = "Diamond",
Spade = "Spade"
}
- まぜこぜもOK
enum Color {
Red = '#c10000',
Blue = '#007ac1',
Pink = 0xc10050, //16進数
White = 255
}
ところで "typescript enum" で検索してみましょう
あとプログラミングTypescriptの注釈も見ましょう
列挙型の安全な使用には落とし穴が伴うため、列挙型の使用は控えることをお勧めします。 TypeScript には、ほかにもよい表現方法がたくさんあります。 それでも、共同作業者が列挙型の使用を主張し、彼らの考えを変える手立てがない場合は、 彼らが外出している間にいくつかの TSLint ルールを忍者のように忍び込ませ、数値によるア クセスと const でない enum について警告が出るようにしてください。
列挙体は危険な型
- 数値型の列挙体は数字ならなんでも代入できる
-
const
ではない列挙体はインデックスアクセスで存在しない要素にアクセスできる
enum Suit {
Heart,
Club,
Diamond,
Spade
}
let cardOfHeart: Suit = Suit.Heart
cardOfHeart = 1 //!!!
let suitInt:number = cardOfHeart
let club = Suit[1] //OK
let invalid = Suit[5] //!!!
console.log(invalid) //undefined
安全に列挙体を使うために
-
- 文字列型を必ず利用する
-
- const enum にして範囲外アクセスを避ける
const enum SuitString {
Heart = "Heart",
Club = "Club",
Diamond = "Diamond",
Spade = "Spade"
}
let cardOfDiamond: SuitString = SuitString.Diamond
cardOfDiamond = "Joker" //Type '"Joker"' is not assignable to type 'SuitString'.(2322)
cardOfDiamond = SuitString[2] //A const enum member can only be accessed using a string literal.(2476)
そもそもTypescriptに列挙体は必要か?
- Union型で同様に安全な定義ができる
type SuitUnion = "Heart" | "Club" | "Diamond" | "Spade"
let spade: SuitUnion = "Spade"
- Union型で実現できないことがしたければ使ってもいいが、おそらく限定的
- 要素の列挙3
- ビットフラグ的使い方
前回の課題おさらい①
- 以下のコードはエラーになります。なぜエラーになるかを説明してください。また、エラーにならないように修正してください。
function SomeOrNull(flag: boolean, value: string) {
return flag ? value : null
}
let value = SomeOrNull(true, "Hoge")
console.log(value.length) // error!!
参考回答
関数 SomeOrNull
はUnion型 string | null
を返す関数である。
したがって変数value
の型は string | null
型であるため、このままでは string
型のプロパティである length
を利用できないため、エラーとなる。
修正には、typeof
演算子により、特定スコープ内でのみvalue
の型をstring
と推論できるようにする必要がある。
function SomeOrNull(flag: boolean, value: string) {
return flag ? value : null
}
let value = SomeOrNull(true, "Hoge")
if(typeof value == 'string'){
console.log(value.length)
}
別解(推奨はしませんが、こういう逃げ道もあるということです)
function SomeOrNull(flag: boolean, value: string) {
return flag ? value : null
}
let value:any = SomeOrNull(true, "Hoge")
console.log(value.length)
前回の課題おさらい②
- 交差型や合併型がうまく使えそうな事例を一つ考え、実際に型定義をしてみてください
参考回答
合併型(Union)
関数のレスポンスを、例外ではなく戻り値として表現する。
type Error = {
errorCode: number,
message: string,
causedBy?: Error
}
type MaybeString = "string" | Error
function SomeOrError(flag: boolean, value: string) : MaybeString {
return flag ? value : {errorCode: 1, "flag is not true"}
}
- 他の言語で
Optional
,Maybe
,Either
と呼ばれるもの(の基礎の基礎的な定義)です。
交差型(Intersection)
- 軽量なクラス拡張として利用できる。
type Person = {
firstName: string,
middleName?: string,
familyName: string
}
type Address = {
prefecture: string,
city: string
}
type Questionnaire = {
question: string,
answer: string,
respondent: Person & Address
}
let qa: Questionnaire = {
question: "好きな言語は?",
answer: "typescript",
respondent: {
firstName: "山田",
familyName: "太郎",
prefecture: "Osaka",
city: "Osaka"
}
}
演習問題
以下のコードをenumではなくUnionを使う形に書き換えてください。
enum Tiles {
None = 0,
AllSimples = 1,
AllRuns = 1 << 1,
Reach = 1 << 2,
SelfDraw = 1 << 3,
FirstTurnWin = 1 << 4
}
function printTile(tiles: Tiles):string {
let result = ""
if(tiles & Tiles.Reach){
result += "リーチ"
}
if(tiles & Tiles.FirstTurnWin){
result += "一発"
}
if(tiles & Tiles.SelfDraw){
result += "ツモ"
}
if(tiles & Tiles.AllSimples){
result += "断么九"
}
if(tiles & Tiles.AllRuns){
result += "平和"
}
return result
}
let tile = Tiles.Reach | Tiles.SelfDraw
console.log(printTile(tile)) //リーチツモ
ヒント
以下のような定義をすることで、Union型の要素一覧を持つことができる。(使わなくてもいいです)
const permissions = ['read', 'write', 'execute'] as const;
type Permission = typeof permissions[number]; // 'read' | 'write' | 'execute'
参考: https://www.kabuku.co.jp/developers/good-bye-typescript-enum
-
個人的には、型を最初に設計して明示しておきたいので、一時変数以外は型アノテーションを書きます ↩
-
もちろんHaskellやScalaなどの言語には先行して存在するので「特有」は言いすぎですが、JavaやC#などと比べると、という意味では特有と言っていいのかなと ↩
-
定義を工夫することで、ほぼ同様のことは実現できる→ https://www.kabuku.co.jp/developers/good-bye-typescript-enum ↩