TypeScript 入門日記 05

前回

blog.takunology.jp

今回はこれを進めていく。(本日二本目!)

docs.microsoft.com

クラス

概念とか定義方法は C#Java のいいとこ取りみたいな感じ。継承するときは extends を使うのでやっぱり Java 寄り?

プロパティはフィールド、関数はメソッドになったり、get set といったアクセサーを使えるのもほぼ同じなので実装方法さえ覚えれば使えそう。

ちょっと個人的にクセがあるなぁと思ったのはコンストラクタの宣言かな。C# だとクラスと同名にしたメソッドを宣言すればコンストラクタになったのでちょっと違和感ある。説明だと最大で1つだけ持てるみたいなので、オーバーロードとかはないのかな?

class Car {
    // フィールド
    _make: string;
    _color: string;
    _doors: number;

    // コンストラクタ
    constructor (make: string, color: string, doors = 4) {
        this._make = make;
        this._color = make;
        if ((doors % 2) === 0) {
            this._doors = doors;
        } else {
            throw new Error("Doors must be an even number");
        }
    }

    // アクセサ
    get make() {
        return this._make;
    }
    set make(make) {
        this._make = make;
    }
    get color() {
        return "The color of the car is " + this._color;
    }
    set color(color) {
        this._color = color;
    }
    get doors() {
        return this._doors;
    }
    set doors(doors) {
        if ((doors % 2) === 0) {
            this._doors = doors;
        } else {
            throw new Error("Doors must be an even number");
        }
    }

    // メソッド
    accelerate(speed: number): string {
        return `${this.worker()} is accelerating to ${speed} MPH.`
    }
    break(): string {
        return `${this.worker()} is breaking with the standard breaking system.`
    }
    turn(direction: "left" | "right"): string {
        return `${this.worker()} is turning ${direction}`;
    }
    worker(): string {
        return this._make;
    }
}

インスタンス

これも C# と同じ。

// インスタンス
let myCar1 = new Car("Cool Car Company", "blue", 2);
let myCar2 = new Car("Galaxy Motors", "red", 4);
let myCar3 = new Car("Galaxy Motors", "gray");

console.log(myCar1.color);
console.log(myCar1._color + "\n");

console.log(myCar3.doors + "\n");

console.log(myCar1.accelerate(35));
console.log(myCar1.break());
console.log(myCar1.turn("left"));

使うときもドット演算子を使えば参照可能。あと、例外をスローできるので、ドアの枚数を奇数にしてみる。

D:\GitHub\web-practice\typescript-mslearn\05\build\ex01.js:11
            throw new Error("Doors must be an even number");
            ^

Error: Doors must be an even number
    at new Car (D:\GitHub\web-practice\typescript-mslearn\05\build\ex01.js:11:19)
    at Object.<anonymous> (D:\GitHub\web-practice\typescript-mslearn\05\build\ex01.js:54:14)

問題なければこんな感じの結果になる。

The color of the car is Cool Car Company
Cool Car Company

4

Cool Car Company is accelerating to 35 MPH.
Cool Car Company is breaking with the standard breaking system.
Cool Car Company is turning left

アクセス修飾子

これも C# と同じく、フィールドやメソッドの前に付けてあげるだけ。

class Car {
    private _make: string;
    private _color: string;
    private _doors: number;

    public worker(): string {
        return this._make;
    }
}

静的クラス

これも C# と同じ。静的にしたい場合は static キーワードをつけるだけ。フィールド参照もクラス名自体へドット演算子をつけるだけ。

class Car {
    private _make: string;
    private _color: string;
    private _doors: number;

    public worker(): string {
        return this._make;
    }

    constructor(make: string, color: string, doors = 4) {
        this._make = make;
        this._color = color;
        this._doors = doors;
        // クラス名にドット演算子を作用
        Car.numberOfCars++; 
    }
}

クラスの継承

これは Java に近い感じ。基底クラスのパラメータを含めて利用するときは super を使う。

class ElectricCar extends Car {
    private _range: number;

    constructor (make: string, color: string, range: number, doors = 2){
        super(make, color, doors);
        this._range = range;
    }

    get range() {
        return this._range;
    }
    set range(range) {
        this._range = range;
    }

    charge() {
        console.log(this.worker() + "is charging.");
    }

    // 基底クラスのメソッドをオーバーライド
    break(): string {
        return `${this.worker()} is breaking with regenerative braking system.`;
    }
}

let spark = new ElectricCar("Spark Motors", "silver", 124, 2);
let eCar = new ElectricCar("Electric Car Co.", "black", 263);
console.log(eCar.doors);
spark.charge();

console.log(spark.break());

インターフェイスの利用

そのクラスに実装すべきコードコントラクト(実装条件)を課すことができる。

interface Vehicle {
    make: string;
    color: string;
    doors: number;
    accelerate(speed: number): string;
    brake(): string;
    turn(direction: 'left' | 'right'): string;
}

// クラスへ作用させる
class Car implements Vehicle {
    
}

インターフェイスとクラスの使い時

インターフェイスによるデータ構造の定義を行えば、クライアントとサーバで共有できる。例えば、犬に関するインターフェイスを作り、犬に関するデータを取得する例を考える。 loadDog メソッドを呼び出せば、その構造を知ることができる。

interface Dog {
    id?: number;
    name: string;
    age: number;
    description: string;
}

async loadDog(id: number): Dog {
    return await (await fetch('サーバURL')).json() as Dog;
}

クラスの場合は実装の中身が定義できるので、直接データを操作するためのメソッドを呼び出しができる。

class DogRecord implements Dog {
    id: number;
    name: string;
    age: number;
    description: string;

    constructor({name, age, description, id = 0}: Dog) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.description = description;
    }

    static load(id: number): DogRecord {
        // データベースから読み込む
        return dog;
    }

    save() {
        // データベースに書き込む
    }
}

課題

最後に恒例の MS Learn の課題演習をやってオワリ。

class BuildArray {
    private _items: number;
    private _sortOrder: "ascending" | "descending";

    constructor (items: number, sortOrder: "ascending" | "descending") {
        this._items = items;
        this._sortOrder = sortOrder;
    }

    get items() {
        return this._items;
    }
    set items(items) {
        this._items = items;
    }
    get sortOrder() {
        return this._sortOrder;
    }
    set sortOrder(sortOrder) {
        this._sortOrder = sortOrder;
    }

    private sortDescending = (a: number, b: number) => {
        if(a > b) {
            return -1;
        } else if (b > a) {
            return 1;
        } else {
            return 0;
        }
    }

    private sortAscending = (a: number, b: number) => {
        if (a > b) {
            return 1;
        } else if (b > a) {
            return -1;
        } else {
            return 0;
        }
    }

    public buildArray(): number[] {
        let randomNumbers: number[] = [];
        let nextNumber: number;
        for (let counter = 0; counter < this.items; counter++) {
            nextNumber = Math.ceil(Math.random() * (100 - 1));
            if (randomNumbers.indexOf(nextNumber) === -1) {
                randomNumbers.push(nextNumber);
            } else {
                counter--;
            }
        }
        if (this._sortOrder === 'ascending') {
            return randomNumbers.sort(this.sortAscending);
        } else {
            return randomNumbers.sort(this.sortDescending);
        }
    }
}

let testArray1 = new BuildArray(12, 'ascending');
let testArray2 = new BuildArray(8, 'descending');
console.log(testArray1.buildArray());
console.log(testArray2.buildArray());

実行結果

[
   2,  9, 15, 50, 55,
  63, 66, 82, 88, 90,
  95, 99
]
[
  92, 89, 87, 81,
  47, 38, 28, 16
]

今回もソートだけど、クラスを使っているので呼び出し側が簡潔になっている?とは思う。 体感だと、だんだん C# (Java) に近づいている感じがする。

イベントのお知らせ

2022年4月24日(日)に MS Tech Camp #14 を開催します。今回は私「たくのろじぃ」主催で、Blazor を使ったポートフォリオサイト製作と Azure Static Web Apps へのデプロイをやります。
興味がある方は(社会人、学生問わず)ぜひご参加ください!
Youtube での配信ですので、休日はごろごろしながらご覧いただければと思います。もちろん、一緒にやっていただいても結構です!

mspjp.connpass.com