JavaScript
TypeScript
ECMAScript

【TypeScript】TypeScriptの雰囲気を掴もう


はじめに

ざっくり静的型付け言語とES6よりも拡張されたクラスベースを理解することが目的。

TypeScriptを知らない人、静的型付け言語を使ったことがない人、Java等のオブジェクト指向言語を使ったことがない人向け。

自分自身も最近はじめたため誤りがあれば是非教えてくださいmm


TypeScriptの特徴


  • 静的な型システム

  • クラスベースのオブジェクト指向の拡張


参考


TypeScriptの実行

TypeScriptをインストール

npm install -g typescript

hello.tsを作成


hello.ts

function greeter(person: string) {

return "Hello, " + person;
}

geeter('taro')


コンパイル

tsc hello.ts

hello.jsがつくられる


hello.js

function greeter(person) {

return "Hello, " + person;
}
greeter('taro');

コンパイルが失敗するようにhello.tsを修正


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