1. Typescriptとは
Typescriptについての基礎情報
- 2012年に登場した比較的新しい言語
- Javascriptの厳密なスーパーセットとして作られている
- 型を活用することによる、「スケールするJavascript」を目指している
新しい言語、Typescript
- 登場は2012年
- Goは2009年、Rustが2010年、Dartが2011年
- 言語設計者はAnders Hejlsberg(C#やDelphiを作った人)
- MicrosoftがOSSとして開発している
- Angularを始め、多くのJavascriptフレームワークがメイン言語基盤として採用
Javascriptのスーパーセット、Typescript
- すべてのJavascriptコードは、ValidなTypescriptコードとして扱われる
- Javascriptに関する知識は必須と言っていい
- Typescript自身もJavascriptにコンパイルされてブラウザ上で実行される
- いわゆるAltJSの一つとも言える
- Typescript自身の仕様がJavascript本体に取り込まれる傾向が強い
- もちろん、NodeJS上でも動く(サーバーサイドプログラミングにも利用できる)
- Node Lambdaにも利用可能
他のAltJS言語との比較
- JSにコンパイルされる言語としては、古くはCoffeeScript、最近でもPureScriptやElmなど数多い
- 他の言語に比べて、TypescriptはJSとの相互運用性が非常に強い
- 極論JSのままでもコンパイルは通る
- 既存のJSプロジェクトのソースコードをそのまま利用することも可能
- 型定義はあったほうが良いが必須ではない
型がある"スケールするJavascript"
- 公式ホームページでも "Javascript that scales" と謳われている
- 「スケールする」とは「大規模開発に向く」ということ
- 静的型付されているので、コンパイラという大きな武器が使える
- 実行時ではなく、コードを書いているときに多くのエラーが検出できる
Typescriptが見つけるエラー
let x = 1 + [] //Operator '+' cannot be applied to types
// 'number' and 'never[]'.(2365)
let obj = {}
obj.hoge // Property 'hoge' does not exist on type '{}'.(2339)
function half(b: number) {
return b / 2
}
half("3") // Argument of type '"3"' is not assignable
// to parameter of type 'number'.
Typescriptの型システム
- 静的型付けされた、コンパイル時にエラーを検出できるシステム
- 設計上の規約を、ある程度コンパイラに落とすことができる
- リテラル型、合併型、部分型など、JavaやC#にもないような高度な型システムを備える
- ScalaやHaskellなどの影響を強く受けている
- 型が語れる範囲が非常に広い
Typescriptの型付で表現できる制約
- String型ではなく、特定の文字しか入らない
- 電話番号には特定の書式の数字文字列しか入れられない
- 特定のメソッドを呼んでからでないと、別のメソッドが呼べない
//強力な型付けの例:
//URLとMethodが設定されていないでsendを送るとCompile Errorになる
new SafeRequestBuilder()
.setUrl("http://yahoo.co.jp")
//.setMethod('GET')
.send() //Compile Error!!
閑話休題: 型システムでここまでできる
2. Typescriptと型
そもそも型とはなにか?
型(type)
値と、それを使ってできる事柄の集まり
例えば、、、
- String型: 文字列と、それをつかう演算や操作のかたまり
- number型: 数値と、それを使う演算や操作のかたまり
馴染みのある言葉で言えば、フィールドとメソッドをまとめたものが型。
Typescript の標準型
型アノテーション
//数値型
let a:number = 1
//文字列型
let b:string = 'hello'
//論理値型の配列型
let c:boolean[] = [true, false]
//型が違えば代入は弾かれる
a = b //Type 'string' is not assignable to type 'number'.(2322)
型推論
自明な型は推論できる。
//数値型
let a = 1
//文字列型
let b = 'hello'
//論理値型の配列型
let c = [true, false]
//型推論でも同様のエラーは出る
a = b //Type 'string' is not assignable to type 'number'.(2322)
関数の型アノテーション
// number => number の関数
function half(input: number): number {
return input / 2
}
// 戻り値は型推論可能
function half2(input: number) {
return input / 2
}
3. Typescriptの基礎型
any
- jsや外部APIとの相互運用性のためだけに存在する
- 撲滅が史上命題、あるだけで基本的には害悪
これは最後の手段の型であり、できるだけ避けるべきです。
プログラミング JavaScript
any
をなぜ避けるのか
-
any
にはどんな型の値でも代入できる -
any
はどんな型の変数にも代入できる - あらゆる型システムを崩壊させ、Typescriptのコンパイラを無用の長物にする鬼子
- 暗黙的な
any
を禁止するオプションも存在する
let a:any
a = 1
a = '1'
a = true
a = {}
a = () => {}
any
の使い所
基本的には使わないが、自分からは制御しきれない外部の値とのインタフェースのために利用する場面はある。
- 外部APIなどで利用するJSONの型がどうしても確定できないなど、コード外の不安定性に対処する
- 外部ライブラリのバグなどで、本来定義されている型ではないオブジェクトをどうしても代入する必要があるとき
let anyArg:any = {key: 'value'}
let ext = new ExternalClass(stringArg: anyArg)
unknown
- 変数宣言時点では型が推測不可能である、というのを示す型
- 変数を型に束縛しないと、そもそも殆どの操作ができないようになっている
let unknownVal: unknown
let someCondition = true
if(someCondition){
unknownVal = "string"
}
else {
unknownVal = 3
}
if(typeof unknownVal == "string"){
//このスコープでは文字列型に解釈される
unknownVal.toLowerCase()
}else if(typeof unknownVal == "number") {
//このスコープでは数値型に解釈される
let num:number = unknownVal
}
unknown
と any
の違い
-
unknown
はあくまで限定的な利用を想定しており、型を確定させないと実行できる操作が制限される -
any
は型を確定させないままの継続的な利用を想定しており、利用制限がない。- エラーを埋め込むようなコードを書きやすい
- 宣言時点で型が本当に確定できない場合は、
any
ではなくunknown
を利用すべき
let a: any = 666
let b: any = ['danger']
let c = a + b // no error
let d: unknown = 666
let e: unknown = ['danger']
let f = d + e // Object is of type 'unknown'.(2571)
null
と Undefined
- 両方とも「空」を表す
-
null
は未設定、undefined
は未定義、というニュアンスがあるが、実質的には同じと思って良い
let a = undefined
let b = null
console.log(a == b) //true
console.log(a === b) //false
void
と never
- 両方とも関数の戻り値で多く利用される
-
void
は何も返さない、ということを表す -
never
はその関数は何も返さない、ということを表す
function c():void {
let a = 2 + 2
let b = a * a
}
function e():never {
while (true) {
console.log("loop")
}
}
// neverをリターンに取る場合は関数内から出ることは許されない
function c2():never { //A function returning 'never' cannot have a reachable end point.(2534)
let a = 2 + 2
let b = a * a
}
symbol
- ユニーク性を保証したオブジェクトで、同一キーから生成されたSymbolも異なるものとして評価される
- KeyValueのKey値などに用いられる
-
unique
キーワードを付けることで、コンパイラにもその値の一意性を伝達できる
let strOfA = "A"
let strOfA2 = "A"
console.log(strOfA === strOfA2) // true
let symbolOfA = Symbol("A")
let symbolOfOfA2 = Symbol("A")
console.log(symbolOfA == symbolOfOfA2) // false
console.log(symbolOfA === symbolOfOfA2) // false
const uSymbolA: unique symbol = Symbol("A")
const uSymbolA2: unique symbol = Symbol("A")
console.log(uSymbolA == uSymbolA2) // This condition will always return 'false'
// since the types 'typeof uSymbolA' and 'typeof uSymbolA2'
// have no overlap.(2367)
その他の基礎型
その他、おなじみの基礎型もあります。基本的にはJavascriptと同一なのでMDNなどを参照してください。
-
boolean
-
number
-
bigint
-
string
配列とタプル
- 配列は「同じ型を持ったもの」を複数個まとめて扱うためのもの。
- 特に説明は不要ですよね?(配列操作関数については別途やります)
- タプルはTypescriptにおいては、「要素数が固定され」かつ「要素ごとに型を指定できる」配列のこと。
let array = [1,2,3,4,5] //配列 number[]
let tuple:[string, number, boolean] = ["tuple", 567 , true] //タプル
//要素アクセス方法は配列に似ている
console.log(tuple[0]) //tuple
リテラル型
- 特定の値を表し、それ以外の値は受け付けない型
- 単体ではただの定数にしかならないが、後述する合併型と併用することで大きな表現力を得る
- constは型アノテートしない限りリテラル型として表現される
let strLiteral: "String"
strLiteral = "String"
strLiteral = "Str" //Type '"Str"' is not assignable to type '"String"'.(2322)
オブジェクト型
- いわゆる「クラス」的なもの、ただしクラス定義は必須ではなく、定義時にオブジェクトの型を定義するだけでよい
let book: {
title: string,
author: string,
description: string,
releaseDate: Date
}
book = {
title: "ファクトフルネス",
author: "ハンナ ロスリング",
description: "ファクトフルネスとは――データや事実にもとづき、世界を読み解く習慣。賢い人ほどとらわれる10の思い込みから解放されれば、癒され、世界を正しく見るスキルが身につく。",
releaseDate: new Date(2019, 1, 11)
}
Typescriptの構造的型付け
- Typescriptの型は「構造的型付け」と呼ばれ、保持する構造が同一であれば同一の型としてみなされる。
- JavaやC#は「名前的型付け」と呼ばれ、たとえ構造が同じであっても、その構造につけられた名前が異なれば同一の型とは見なされない
let book = {
title: "ファクトフルネス",
author: "ハンナ ロスリング",
description: "ファクトフルネスとは――データや事実にもとづき、世界を読み解く習慣。賢い人ほどとらわれる10の思い込みから解放されれば、癒され、世界を正しく見るスキルが身につく。",
releaseDate: new Date(2019, 1, 11)
}
let hon: {
title: string,
author: string,
description: string,
releaseDate: Date
}
let memo: {
title: string,
content: string
}
hon = book // OK
memo = book // Property 'content' is missing in type '{ title: string; author: string; description: string; releaseDate: Date; }' but required in type '{ title: string; content: string; }'.(2741)
型エイリアス
- 型定義に対して別名を割り当てる
-
type
キーワードを用いるので、実際には型エイリアスの定義そのものを型定義と感じる人もいるかも
-
- クラスっぽいが、どちらかというとインターフェイスに近い
- 組み込み型にも型エイリアスは作成可能
type Book = {
title: string,
author: string,
description: string,
releaseDate: Date
}
let book: Book = {
title: "プログラミングTypescript",
author: "Boris Cherny",
description: "スケールする JavaScript アプリケーション開発",
releaseDate: new Date(2019, 1, 11)
}
// 組み込み型にもAliasを作成可能
type String2 = string
let string2: String2 = 'hoge'
合併型(Union)
- 複数の型をとる可能性がある、ということを表現する
- この関数はある値かNullを返します
- リテラル型と組み合わせることで、「取りうる値のリスト」のような使い方もできる
let unit = "cm" | "m" | "mm"
function SomeOrNull(flag: boolean, value: string) {
return flag ? value : null
}
交差型(Intersection)
- 複数の型が結合した型を表現する
- 基本的には、結合された型のすべてのメンバーを持つ型となる
- 型の合成を行うことができる
type NormalUser = {
name: string,
email: string
}
type Administration = {
target: string,
allowedAction: "read" | "readwrite"
}
type AdminUser = NormalUser & Administration
let admin: AdminUser = {
name: "John Doe",
email: "jonhndoe@email.com",
target: "Databse",
allowedAction: "readwrite"
}
本日はここまで
- 一気に型の基礎をやりました
- あくまで基礎で、ここからさらに高度な型の利用方法に進んでいきます
- ここまででわからないところはTeamsで聞いてください
演習課題
- 以下のコードはエラーになります。なぜエラーになるかを説明してください。また、エラーにならないように修正してください。
function SomeOrNull(flag: boolean, value: string) {
return flag ? value : null
}
let value = SomeOrNull(true, "Hoge")
console.log(value.length) // error!!
- 交差型や合併型がうまく使えそうな事例を一つ考え、実際に型定義をしてみてください