はじめに
ざっくり静的型付け言語とES6よりも拡張されたクラスベースを理解することが目的。
TypeScriptを知らない人、静的型付け言語を使ったことがない人、Java等のオブジェクト指向言語を使ったことがない人向け。
自分自身も最近はじめたため誤りがあれば是非教えてくださいmm
TypeScriptの特徴
- 静的な型システム
- クラスベースのオブジェクト指向の拡張
参考
- 公式ドキュメント
- 速習TypeScript
- TypeScript Deep Dive
-
Playground
- ブラウザで手軽にTypeScriptを実行できる
- コンパイルされたJavaScriptも見れるので学習じにはおすすめ
- 「ts playground」とかで検索するとアダルトサイトが引っかかってしまうので注意
TypeScriptの実行
TypeScriptをインストール
npm install -g typescript
hello.tsを作成
function greeter(person: string) {
return "Hello, " + person;
}
geeter('taro')
コンパイル
tsc hello.ts
hello.jsがつくられる
function greeter(person) {
return "Hello, " + person;
}
greeter('taro');
コンパイルが失敗するようにhello.tsを修正
function greeter(person: string) {
return "Hello, " + person;
}
geeter(10)
エラーになる
hello.ts:5:9 - error TS2345: Argument of type '10' is not assignable to parameter of type 'string'.
5 greeter(10)
~~
Found 1 error.
データ型
基本
TypeScriptの静的型(Type)システムにおけるデータ型
- number: 数値型
- string: 文字列型
- boolean: 真偽型
- symbol: シンボル型
- any: 任意の型
- object型: 配列型、タプル型、クラス型、インターフェイス型、関数型
let data: string = 'hoge'
data = 'fuga'
data = 100 // エラー
型推論
型を省略するこも可能
変数宣言で型が省略された場合、初期値から型推論が行われる
let data = 100 // 初期値がnumberなので型はnumberだとみなされる
data = 200
data = 'hoge' // エラー
let data2 = 'hoge'
data2 = 'fuga'
data2 = 100 // エラー
let data3 // 初期値を省略すると型はanyとみなされる
data3 = 'fuga'
data3 = 100 //エラーにならない
let data4: any = 100
data4 = 150
data4 = 'hoge' // エラーにならない
let data5 = undefined // 初期値はundefined/nullを指定した場合もany型とみなされる
data5 = 100
data5 = 'hoge' // エラーにならない
型アサーション
互換性のある型であれば、型を明示的に変換できる
function hello(name: string) {
return `${name}さん、こんにちは!`
}
// 数値型を渡すとエラーになる
hello(100)
// any型に変換
hello(<any>100)
// as構文でも変換可能
hello(100 as any)
配列
// 基本
let data: string[] = ['apple', 'orange', 'peach']
console.log(data[0]) // apple
// ジェネリック記法
let data2: Array<string> = ['apple', 'oarange', 'peach']
連想配列
// indexの部分は何でも良い
let obj: { [index: string]: string } = {
'apple': '青森',
'orange': '愛媛',
'peach': '群馬'
}
- 連想配列の注意点
プロパティ名がStringの時に限りドット記法によるアクセスが可能だが、その場合は最初に定義しておく必要がある。
let obj = {
'apple': '青森',
'orange': '愛媛',
'peach': '群馬'
}
obj.grape = '山梨' //エラー
// Property 'grape' does not exist on type '{ 'apple': string; 'orange': string; 'peach': string; }'.
obj['grape'] = '山梨' // ブラケット記法ならプロパティの追加が可能
列挙型
列挙型はユニークな値の有限集合。重複はエラーになる。
enum Fruit {
Apple,
Orange,
Peach
}
let f: Fruit = Fruit.Apple
console.log(f) // 0
console.log(Fruit[f]) // Apple
タプル型
異なる型の集合
let data: [string, number, boolean] = [ 'hoge', 100, true]
型エイリアスをつくることが出来る
type SmapleType = [string, number, string]
let data: SampleType = ['hoge', 100, 'fuga']
ユニオン型
let data: string | boolean
data = 'hoge'
data = false
dat = 100 // エラー
let data2: (string | number)[] = ['hoge', 100, 100]
文字列リテラル型
type Fruit = 'apple' | 'orange' | 'peach'
let data: Fruit = 'apple'
data = 'grape' // エラー
関数
基本
function hello(name: string): string {
return `${name}さん、こんにちは!`
}
hello('tom') //tomさん、こんにちは!
let hello2 = (name: string): string => {
return `${name}さん、こんにちは!`
}
hello2('tom') //tomさん、こんにちは!
返り値がない場合はvoidを指定
function hello(name: string): void {
console.log(name)
}
引数の省略
function hello(name?: string): string {
return name === undefined ? 'スルー' : `${name}さん、こんにちは!`
}
hello() //スルー
引数のデフォルト値
function hello(name: string = 'tom'): string {
return `${name}さん、こんにちは!`
}
hello() //tomさん、こんにちは!
可変長引数
function hello(...names: string[]) {
let greet = 'こんにちは!'
names.forEach(name => {
greet = `${greet}、${name}さん`
})
return greet
}
alert(hello('tom', 'risa')) //こんにちは!、tomさん、risaさん
オブジェクト指向構文
ES6によりクラスベースのオブジェクト指向が登場したが、TypeScriptはそれをさらに拡張する。
具体的には以下のような機能を提供する。
- アクセス修飾子
- インターフェイス
- ジェネリック
基本的なクラス
class Person {
name: string
job: string
constructor(name: string, job: string) {
this.name = name
this.job = job
}
introduce(): string {
return `${this.name}は${this.job}です`
}
}
let tom = new Person('tom', 'エンジニア')
console.log(tom.introduce()) //tomはエンジニアです
let john = new Person('john', 10) // エラー
アクセス修飾子
- public クラスの外からアクセス可能(デフォルト)
- protected 同じクラス、またはその派生クラスからアクセス可能
- private 同じクラスからのみアクセス可能
class Person {
private name: string
private job: string
constructor(name: string, job: string) {
this.name = name
this.job = job
}
public introduce(): string {
return `${this.name}は${this.job}です`
}
}
let tom = new Person('tom', 'エンジニア')
console.log(tom.introduce()) //tomはエンジニアです
console.log(tom.name) //エラー
//Property 'name' is private and only accessible within class 'Person'.
TypeScriptでは以下のようにコンストラクターを書くことも可能
class Person {
constructor(private name: string, private job: string) {
this.name = name
this.job = job
}
public introduce(): string {
return `${this.name}は${this.job}です`
}
}
getter / setter
class Person {
private _age: number
get age(): number {
return this._age
}
set age(value: number) {
if (value < 0) {
throw new RangeError('ageプロパティは整数で指定してください')
}
this._age = value
}
}
let p = new Person()
p.age = 10
console.log(p.age)
getterはプロパティの参照時に呼び出される
setterはプロパティの書き込み時に呼び出される
static修飾子
class Figure {
public static Pi: number = 3.14
public static circle(radius: number): number {
// 静的メンバーにアクセスする時はthisを明示する
return radius * radius * this.Pi
}
}
console.log(Figure.Pi) //3.14
console.log(Figure.circle(2)) //12.56
名前空間
namespace MainApp {
export class Hoge {
//...
}
}
namespace MainApp2 {
export class Hoge {
//...
}
}
let app = new MainApp.Hoge()
namespaceは入れ子にすることもできる
継承
// 継承元のクラス(スーパークラス/親クラス/基底クラス)
class Person {
protected name: string
protected sex: string
constructor(name: string, sex: string) {
this.name = name
this.sex = sex
}
show(): string {
return `${this.name}は${this.sex}です`
}
}
// 継承して出来たクラス(サブクラス/小クラス/派生クラス)
class Engineer extends Person {
work(): string {
return `${this.name}はプログラミングします`
}
}
let engineer = new Engineer('tom', '男')
console.log(engineer.show()) //tomは男です
console.log(engineer.work()) //tomはプログラミングします
オーバーライド
class Person {
protected name: string
protected sex: string
constructor(name: string, sex: string) {
this.name = name
this.sex = sex
}
show(): string {
return `${this.name}は${this.sex}です。`
}
}
class Engineer extends Person {
protected lang: string
constructor(name: string, sex: string, lang: string) {
super(name, sex)
this.lang = lang
}
//showメソッドをオーバーライド
show(): string {
return super.show() + `好きな言語は${this.lang}です。`
}
}
let engineer = new Engineer('tom', '男', 'TypeScript')
console.log(engineer.show()) //tomは男です。好きな言語はTypeScriptです。
抽象メソッド
オーバーライドは派生クラスで基底クラスの上書きを強制しない
抽象メソッドは派生クラスで基底クラスの上書きを強制する
//抽象クラスを宣言
abstract class Figure {
constructor(protected width: number, protected height: number) {}
//abstract修飾子はコードブロックを持たず最後に「;」で区切る
abstract getArea() :number;
}
class Triangle extends Figure {
//抽象メソッドをオーバーライドが必須
getArea(): number {
return this.width * this.height / 2
}
}
let triangle = new Triangle(10, 5)
console.log(triangle.getArea()) //25
インターフェイス
TypeScriptは一度に一つのクラスしか継承できない(単一継承)
インターフェイスは複数のインターフェイスを同時に継承できる(インターフェイスを継承することを実装すると言う)
インターフェイスは全てのメソッドが抽象メソッドである特別なクラス
派生クラスのことを実装クラスと言う
//インターフェイスを宣言
interface Figure {
getArea(): number;
}
class Triangle implements Figure {
constructor(private width: number, protected height: number) {}
getArea() :number {
return this.width * this.heigth / 2
}
}
let triangle = new Triangle(100, 6)
console.log(t.getArea()) //300
- インターフェイスの制限
- メソッドは全て抽象メソッド。抽象メソッドであることは自明なのでabstract修飾子は指定しない
- 全てのメンバーはpublicであることが自明なのでアクセス修飾子は指定しない
- staticメンバーも宣言できない
- インターフェイスを継承してインターフェイスを宣言する
インターフェイスの継承にはimplementsではなくextendsを使う
interface Hoge extends Foo, Bar {}
ジェネリック
汎用的なクラス、メソッドに対して特定の型を紐付けるための機能
class MyGenerics<Ge> {
value: Ge
getValue(): Ge {
return this.value
}
}
let ge = new MyGenerics<string>()
ge.value = 'generic'
console.log(ge.getValue) //generic