Open4

『understanding-typescript』のメモ

K'K'

tsconfig.json

tsc --initで作ることができる。
これを実行すると、実行したカレントディレクトリがプロジェクトのルートディレクトリであることをTypeScriptに伝えることができる。

sourceMap

デバッグを効率的にできる機能。
trueにすると、開発者コンソールのsourceで.tsファイルの内容が見える。加えて、ブレークポイントを設置してdebuggerも利用できる。

仕組み

これは、.tsファイルをコンパイルしたときに.js.mapというファイルができ、jsファイルとtsファイルの何行目と何行目が一致しているかの対応付けを作ってくれる。
この対応付けファイルをブラウザが理解してくれる。

outDir

TypeScriptからコンパイルされたjavascriptファイルの出力先ディレクトリ。
入力先ディレクトリ構造がそのまま反映される。
これによるメリットはTypeScriptのインポートと同じ構造でJavascriptもインポートできる。

rootDir

コンパイル対象のTypeScriptコードを格納する場所。
includeとの違いは、rootDirに指定したディレクトリ以外にtsファイルが存在する場合、コンパイル時にエラーを出してくれる。

動作確認

"rootDir": "./src"
"outDir": "./dist" 
├── src
    ├── analytics.ts
    └── app.ts


dist配下にapp.jsanalytics.jsができる


"rootDir": "./"
"outDir": "./dist" 
├── src
    ├── analytics.ts
    └── app.ts


dist/src配下にapp.jsanalytics.jsができる

removeComment

TypeScriptで描いたコメントはコンパイルされたJSに含まれない。JSファイルのサイズを縮小できる。

noEmitOnError

コンパイルエラーが起きたときにJavascriptファイルを出力しないようにできる

K'K'

ES6から導入されたモダンJavaScript機能のメモ

let/const

従来のvarとの違いとして、スコープの違いがある。
varはグローバルスコープまたは関数スコープのどちらか。
letはブロックスコープ

アロー関数

スプレッド演算子

https://zenn.dev/peishim/articles/a2c12108b9a15c

レストパラメータ

任意の数の引数を配列で受け取れる

配列とオブジェクトの分割代入

const items = ["hoge", "fuga"];
const [item1, item2, ...remainingItems] = items;

const person = {
    name: "hoge",
    age: 30
}
const {name: userName, age} = person;

コンパイルターゲットを意識する

tsconfig.jsonのtargetに指定したバージョンによってコンパイルされるJavascriptの構文は異なる。
なのでES5とか古めのバージョンがtargetだとモダンなJavascriptの構文は使われない。

K'K'

クラス

protected修飾子

継承したクラスからも参照できる

K'K'

高度な型

交差型

https://qiita.com/suema0331/items/a145909db0bcbcc3f949
オブジェクト型の場合は結合対象がもつプロパティがすべて使えるが、
Union型を交差型で定義する場合、結合対象の型が持つ共通の型になる。以下の場合はNumber型になる

type Combinable = string | number;
type Numeric = number | boolean;

type Universal = Combinable & Numeric;

// OK
const a: Universal = 1;
// NG
const b: Universal = "test";

型ガード

実行時エラーを防止するために、型ガードで何かをする前にその値の型をチェックすることができる。

オブジェクトの場合はinを使う

type Admin = {
    name: string;
    privileges: string[];
};

type Employee = {
    name: string;
    startDate: Date;
};

type UnknownEmployee = Employee | Admin;

function printEmployeeInformation(emp: UnknownEmployee) {
    console.log(emp.name);
    // 型ガード
    if ('privileges' in emp) {
        console.log("Privileges: " + emp.privileges);
    }
    // 型ガード
    if ('startDate' in emp) {
        console.log("StartDate: " + emp.startDate);
    }
}

printEmployeeInformation({ name: "manu", startDate: new Date() });

クラスの場合はinstanceofを使う

class Car {
    drive() {
        console.log("Driving...");
    }
}

class Truck {
    drive() {
        console.log("Driving a truck...");
    }

    loadCargo(amount: number) {
        console.log("Loading cargo..." + amount);
    }
}

type Vehicle = Car | Truck;

const v1 = new Car();
const v2 = new Truck();

function useVehicle(vehicle: Vehicle) {
    vehicle.drive();
    // if ('loadCargo' in vehicle) {
    // クラスでの型ガード
    if (vehicle instanceof Truck) {
        vehicle.loadCargo(1000);
    }
}

useVehicle(v1);
useVehicle(v2);

ただし、interfaceの場合はinstanceofで判別することはできない。interfaceはJavascriptにコンパイルされないから。interfaceの場合はinを使う。

Discriminated Unions

// Discriminated Unions
interface Bird {
    // 共通の識別可能なプロパティを定義する(これはリテラル)
    hoge: "bird";
    flyingSpeed: number;
}

interface Horse {
    // 共通の識別可能なプロパティを定義する(これはリテラル)
    hoge: "horse";
    runningSpeed: number;
}

type Animal = Bird | Horse;

function moveAnimal(animal: Animal) {
    let speed;
    // switch文で判別する
    switch (animal.hoge) {
        case "bird":
            speed = animal.flyingSpeed;
            break;
        case "horse":
            speed = animal.runningSpeed;
    }
    console.log("Moving at speed: " + speed);
}

console.log(moveAnimal({ hoge: "bird", flyingSpeed: 10 }));

型キャスト

その値が特定の型であることをTypeScriptに伝えたい場合に使用する

// 型キャスト
// 1. キャスト対象の前に<>をつける
// const userInputElement = <HTMLInputElement>document.getElementById("user-input")!;

// 2. asをつける(JSXなどで使用する場合、競合してしまうので)
// エクスクラメーションマーク(!)は、返す値がNullではないことをTypeScriptに伝える仕組み
// const userInputElement = document.getElementById("user-input")!;
const userInputElement = document.getElementById("user-input");
// userInputElement.value = 'こんにちは';

// 3. nullかどうかを確認した後にキャストする
if (userInputElement) {
    // キャスト式を()で囲む
    (userInputElement as HTMLInputElement).value = 'こんにちは';
}

インデックス型

オブジェクトのフィールド名をあえて指定せず、プロパティのみを指定したい場合に使えるのがインデックス型(index signature)

// index型
interface ErrorContainer { // { email: '正しいメールアドレスを入力してください', username: 'ユーザー名を入力してください' }
    [prop: string]: string;
}


const errorBag: ErrorContainer = {
    // stringに変換できるので、numberでもOK
    1: "正しいメールアドレスを入力してください",
    username: "ユーザー名を入力してください",
};

関数オーバーロード

// 関数オーバーロード
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: string, b: number): string;
function add(a: number, b: string): string;
function add(a: Combinable, b: Combinable) {
    if (typeof a === "string" || typeof b === "string") {
        return a.toString() + b.toString();
    }
    return a + b;
}

const result = add("'Hello'", "World");
result.split(" ");

Optional Chainig

// Optional chaining
const fetchedUserData = {
    id: "u1",
    name: "Max",
    // job: {
    //     title: "CEO",
    //     description: "My own company"
    // },
};

// 先頭から存在しているかどうかを判断する
console.log(fetchedUserData?.job?.title);

Null合体演算子

// Null合体演算子
// const userInput = null;
const userInput = '';

// null or undefined の場合、??の右側の値を返す
const storedData = userInput ?? "DEFAULT";

console.log(storedData);