Open12

TypeScritp基礎のメモ

ろろろろろろ

記事にするほどではない(と思う)内容をここに書く。

ろろろろろろ

型注釈と型推論

型注釈

明示的に型を指定すること。変数名: 型名

let myouji: string = "苗字";

型推論

初期値に基づいて型を推論する。

let myouji = "苗字"; //初期値からstring型と推論
let nenrei = 100; //初期値からnumber型と推論
let isBoolean = true; //初期値からboolean型と推論
let sampleArray1 = ["国名","県名","市区町村名"]; //初期値からstring型の配列と推論
let sampleArray2 = ["地名", 100-1] //初期値からstring型 | number型の配列と推論(すごい!)
let sampleArray3 = ["文字", 99999, true]; //初期値からstring型 | number型 | boolean型の配列であることを推論(!?!?)
ろろろろろろ

暗黙的な型変換

JavaScript.js
let sampleNum = 5 + true;
console.log(sampleNum);

これを実行すると、コンソールには6が出力される。

JavaScriptでは、Trueを暗黙的に型変換して数値の1として扱う。
よって、5 + true5+1となる。(ちなみにfalse0として扱う)

TypeScriptでは、暗黙的な型変換を行わないためエラーとなる(error:演算子 '+' を型 'number' および 'boolean' に適用することはできません。)

ろろろろろろ

リテラル型

変数の値を具体的な値に限定する。
const seijin = 18;を例にすると、seijinの型名が18になっている。これがリテラル型。
(seijin18以外の値を持つことができない。)

letでもリテラル型を宣言できる。

//Boolean
let literalBool: true = true;
//string
let literalString: `sampleString`;

リテラル型にリテラル型以外の値を代入しようとするとエラーとなる。

ろろろろろろ

ユニオン型

変数に複数の型を指定する。
ユニオン型を整形する型(number | string |...の部分)は、「メンバー」と呼ぶ。

//number型 と string型 を id に指定
let id: number | string;

メンバーに指定した型の値のみを代入できる

id = 10; //number型だからOK
id = "10"; //string型だからOK

メンバーに指定していない型の代入はエラー

id = true; //boolean型はメンバーに存在していないからエラー

ユニオン型とリテラル型を組み合わせると・・・

リテラル型に指定した型のみを変数に代入できるようになる。

let size: "small" | "medium" | "large";
size = "small";

VSCodeのコード補完にも、メンバーが表示される。

メンバーに存在しない値をセットしようとすると、エラーになる。

let size: "small" | "medium" | "large";
size = "mettya dekkaino"; //メンバーに存在しないからエラー

ろろろろろろ

型エイリアス

特定の型に名前を割り当てて、再利用できるようにする。(予約語は使用できない)
パスカルケースで記述されるのが慣例。

// number型 と string型 のユニオン方に ShouhinCode という名前を付ける
type ShouhinCode = number | string;
// リテラル型のユニオン型に Yakushoku という名前をつける
type Yakushoku = "shacho" | "kacho" | "hira";

let shohin:ShouhinCode; // 変数 shohin に ShouhinCode型 を指定


ShohinCodeに存在しない型を代入しようとするとエラーになる。

型エイリアスのユニオン型

型エイリアスはほかの型と組み合わせて使用することも可能。
宣言する際は、使用する順番に宣言しなくてもOK。

type Pc = Windows | Mac; //Windows型 と Mac型のユニオン型 PC を宣言

type Windows = "note" | "desktop";
type Mac = "MackBook" | "iMac" | "Mac Stuio";

let myPc: Pc = "desktop"; // 型を指定するときは、Windows型 と Mac型どちらも指定できる

もちろん、Pc型に存在しない型は指定できない。

ちなみに・・・

jisakuという新たな型を作成。
Pc型とjisaku型のユニオン型にPc2という名前を付けて宣言することも可能。
この時、指定できる値はWindows型、Mac型、jisaku型の3種類。

ろろろろろろ

オブジェクト型

基本

string型を持つnameプロパティと、number型を持つseitoNoプロパティを宣言。(ここではTypeScriptが型推論をしてくれている)
変数名はseitoとする。

const seito = {
  name: "alice",
  seitoNo: 99,
};

seitoに存在しないプロパティを指定するとエラー。

//プロパティ 'address' は型 '{ name: string; seitoNo: number; }' に存在しません。
console.log(seito.address);

seitoNoプロパティにstringを代入しようとするとエラー。

//型 'string' を型 'number' に割り当てることはできません。
seito.seitoNo = "30"; 

型注釈によるオブジェクトの生成

オブジェクト型を型注釈で指定するのは、変数名に次のように記述する。
型を指定したら、=で値を代入する。

let notebook: {
  title: string;
  owner: string;
  editionNumber: number;
} = {
  title: "国語",
  owner: "太郎",
  editionNumber: 5,
};

これだと変数が増えてきたときに非常に読みにくくなってしまうから、型エイリアスを使うことによってすっきりさせることとができる。

type Notebook = {
  title: string;
  owner: string;
  editionNumber: number;
};

const JapaneseNote: Notebook = {
  title: "国語",
  owner: "太郎",
  editionNumber: 5,
};

今までの型宣言と同様に、コード補完にはNotebookで指定した型しか表示されない。
また、存在しない型を指定しようとするとエラーになる。

プロパティが不足している場合

代入されるオブジェクトにが不足している場合は、TypeScriptがエラーを報告する。
次の例では、editionNumberをコメントアウトしているためエラーとなる。

const physicsNote: notebook = {
    title: "物理",
    owner: "次郎",
    // editionNumber: 2, コメントアウト
}

ネストされたオブジェクト

type Shcool = {
  gakunen: number;
  kousha: string;
  floor: number;
  classroom: {
    classCode: number;
    tannin: string;
    ninzu: number;
  };
};

ネストされているオブジェクト型を型エイリアスで書き換えることも可能。

type Classroom = {
  classCode: number;
  tannin: string;
  ninzu: number;
};

type Shcool2 = {
  gakunen: number;
  kousha: string;
  floor: number;
  classroom: Classroom; //型エイリアスを指定
};

変数を宣言するときは次のようになる。

let KinjonoGkko:Shcool2 = {
    gakunen:999,
    kousha:"第○○校舎",
    floor:99,
    classroom:{
        classCode:1,
        tannin:"xx先生",
        ninzu:999
    }
}

もちろん、一部をコメントアウトしていたらエラー。

過剰プロパティチェック

変数mathTeacherに代入されているオブジェクトが型定義にないseibetsuプロパティを所持しているときはエラーになる。

type Kyoshi = {
  name: string;
  age: number;
};

//OK
const japaneseTeacher: Kyoshi = {
  name: "国語の先生",
  age: 999,
};

//エラー
const mathTeacher:Kyoshi = {
    name: "数学の先生",
    age: 999,
    seibetsu:"female"
    //型 '{ name: string; age: number; seibetsu: string; }' を型 'Kyoshi' に割り当てることはできません。
  オブジェクト リテラルは既知のプロパティのみ指定できます。'seibetsu' は型 'Kyoshi' に存在しません。
}

あらかじめ、オブジェクトを生成し変数mathTeacherに代入。
その後、Kyoshi型として宣言した新しい変数を代入すると・・・?

const physicsTeacher = {
  name: "物理の先生",
  age: 999,
  seibetsu: "female",
};

const physicsTeacherKoshi: Kyoshi = physicsTeacher;

physicsTeacherseibetsuプロパティを所持しているが、エラーにはならない。
既存のオブジェクトを別の変数に代入する場合は、過剰なプロパティチェックを行わない。

オプショナルプロパティ

オブジェクトのプロパティに?を追加することによって、任意(オプショナル)のプロパティとして宣言することができる。
変数にオプショナルプロパティが存在しなくてもエラーにはならない。

type Yakushoku = {
  yakuName: string;
  teatcherNmae: string;
  gakunen?: number;
};

const sensei: Yakushoku = {
  yakuName: "一般",
  teatcherNmae: "先生",
  gakunen: 2,
};

const kocho: Yakushoku = {
  yakuName: "校長",
  teatcherNmae: "なまえだよ",
  //gakuen は存在しないがエラーにはならない
};

オプショナルプロパティとしたgakunennumber型とundefined型のユニオン型として扱われる。

?を使用せずに、number | undefinedと明示的に指定するとオプショナルプロパティとして省略することはできない。

type Yakushoku2 = {
  yakuName: string;
  teatcherNmae: string;
  gakunen: number | undefined;
};

const sensei2: Yakushoku2 = {
  yakuName: "校長",
  teatcherNmae: "なまえだよ",
  //gakuen: 1
};

読み取り専用

readOnlyキーワードの宣言によって、後から変更することができないプロパティを宣言できる。

type rekidaiSchoolName = {
  readonly name: string;
  year: number;
};

const schoolName: rekidaiSchoolName = {
  name: "初代の名前",
  year: 2000,
};

//yearの変更が可能
schoolName.year = 1998;

//nameは読み取り専用のため変更不可
schoolName.name = "間違えた名前を上書き";

ろろろろろろ

Array型とTuple型

Array型

他のプログラミング言語同様の配列。
これまで同様に、型を指定しなければ型推論する。

const suuti = [1, 2, 3, 4, 5];//number[]型(型推論)
const kojinNames = ["太郎", "次郎", "三郎"];//string[]型(型推論)

指定された型以外の値を配列に格納しようとするとエラー。

suuti.push("6");


型注釈を行うには変数名:型名[]とする。

let fruits: string[] //型注釈によるArray型の指定
fruits = ["Apple", "Grape", "Banana", "Peach", "pear"];
console.log(fruits[0].toUpperCase()); // → APPLE

Tuple型

TypeScriptには、JavaScripには存在しないTuple(タプル)という型が存在する。
固定された長さを持ち、各要素に対して特定の型が指定できる。
コンパイル時には、JavaScriptの配列(Array)に変換される。
文法は変数名:[型名1,型名2...]
次の例は、loginStatus0'番目がsting型、1番目がboolean`型のTuple型。

let loginStatus: [string, boolean] = ["ログイン名", true]; 

変数loginStatusTuple型で定義されていて、2つの要素を持つことが指定されている。
3つ目の要素としてログイン回数の数値を代入しようとすると型の不一致によるエラーとなる。

Tuple型に存在しないインデックスにアクセスしようとするとエラー。

ラベル付きのTuple型

Tupleにラベルを付与することによって、各要素がどんなデータを表しているかを直接確認できるようになる。
もちろん、指定している型と異なる型を代入しようとするとエラーになる。

type ShingoColor = [red: number, green: number, blue: string];
const shingo: ShingoColor = [1, 2, 'three'];
const shingo2: ShingoColor = [1, 2, 3];


ラベル末尾に?を付けることによって、該当の要素をオプショナル(任意)として宣言可能。
さらに、スプレッド構文...を使って、複数の要素を含むことが可能。

type TupleSample = [first: number, second?: string, ...rest: any[]];
let data1: Foo = [1]; //first要素のみの配列を代入
let data2: Foo = [1, "hello"]; //firstとsecond要素のみを代入
let data3: Foo = [1, "hello", true, 10, "world"]; //first,second要素の他に、...restに複数の要素を割り当て
ろろろろろろ

インターセクション型

組み合わされたすべての型の特性を持つ。記法としては「AかつB」の型だと考えるとよい。

type MultiType = TypeA & TypeB & TypeC;

//ダミー
type TypeA = {
    type_a: string
}
type TypeB = {
    type_b: number
}
type TypeC = {
    type_c: boolean
}

Cpu型とMemory型の2つの型をインターセクション型を使って結合して、Pc型を作成。
Pc型は、Cpu型とMemory型の両方がもつプロパティを含む必要がある。

type Cpu = {
    cpu: string
    core: number
}
type Memory = {
    memorySize: number;
}
type PcType = Cpu & Memory;

const myPc: PcType = {
    cpu: 'intel',
    core: 8,
    memorySize: 16
}

片方の型を持つプロパティが指定されていなければエラー。

const friendPc: PcType = {
    cpu: 'intel',
    core: 8,
}


VSCodeの候補にPcType型が表示される

console.log(`cpu: ${myPc.cpu} core: ${myPc.core} memory: ${myPc.core}`);// cpu: intel core: 8 memory: 16

ろろろろろろ

any型

型の制約なしに任意の値を受け入れる型。
any型の変数には型チェックが適用されない。

let anyValue1: any = 1; //any型
anyValue1 = "noTypeCheck"; //any型
anyValue1.oncheck(); //型チェックが行われないのでエラーにならない

コンパイル自体は問題なく通るが、実際に処理されるタイミングでエラーとなる。anyValue1.oncheck()において、以下のようなエラーが発生する。

 value1.oncheck();
        ^
 TypeError: value1.oncheck is not a function
ろろろろろろ

unknown型

型の安全性を維持しながら未知の型の値を扱うことができる型
any型と同じように、どんな値でも代入できるようになるが、unknown型は変数の型チェックを無視するわけではない。

let unknownValue1: unknown = 1; //unknown型


number型などの型にunknown型を代入することはできない

しかし、unknown型にunknown型を代入することは可能(同一の型だから)

let unknownValue3: unknown = unknownValue1;

unknown型の算術演算子

算術演算子は許可されていない。

let sumValue1 = unknownValue1 + 5;
let sumValue2 = unknownValue1 * 5;



unknown型の変数の操作は限られているが、比較演算子は許可されている。(==,!=,===,!==,>,<,>=,<=,)

let unknownValue4: unknown = 'a';
let unknownValue5: unknown = 'a';
if (unknownValue4 === unknownValue5) {
    console.log('OK');
} else {
    console.log('NG');
}

>> OK

unknown型の変数が「一体何の型なのか」を確認し、確認がとれたらその変数を確認した型として扱うことができる。

ろろろろろろ

関数と型

TypeScriptでは型推論の機能があるので、「関数でも型推論してくれるだろう」という感覚で関数addNumberを定義するとエラーになる。

function addNumbers(a, b) {
    return a + b;
}


なぜならaddNumbers関数の定義から、パラメータa,bは初期化されておらず型推論のヒントがないから。
このパラメータa,bの型は、関数を書いた人しか正しく推論できない。
関数が受け取る引数が数値であることを保証するために、パラメータa,bに明示的に型を指定する。
文法は:型名

function addNumbers(a: number, b: number) {
    return a + b;
}

また、戻り値にも型を明示的に指定することが可能。文法は関数名(パラメータ):戻り値の型

function addNumbers(a: number, b: number): number {
    return a + b;
}

関数型

次の例では、変数sampleFunctionに関数型を指定している
sampleFunctionnumber型のhikisu1string型のhikisu2を受け取り、 boolean`型の値を返却する関数型となる。

let sampleFunction: (hikisu1: number, hikisu2: number) => boolean;

変数の型として、関数型を指定しその変数にアロー関数を代入する場合は・・・

const arrowVariable: (hikisu1: number, hikisu2: number) => number = (a, b) => a + b;

分解してみる

  1. 関数型の定義
    hikisu1hikisu2はどちらもnumber型であり、戻り値もnumber型である」ということを示す。
(hikisu1: number, hikisu2: number) => number
  1. アロー関数
    (a, b) => a + b

「引数にabを受け取り、それを足し算して戻り値として返却する」ということを示す。
5. 変数arrowVariableの定義

const arrowVariable: (hikisu1: number, hikisu2: number) => number = (hikisu1, hikisu2) => hikisu1 + hikisu2;

変数arrowVariable1. 関数型の定義で定義した型と一致するアロー関数を代入する。
arrowVariableは、number型の2つの引数hikisu1hikisu2を受け取り、その合計をnumber型として返す関数になる。

このコードを読みやすくするには、型エイリアスを使用するとよい。

type SampleArrowFunction = (hikisu1: number, hikisu2: number) => number;
const sampleAddNumbers: SampleArrowFunction = (a, b) => a + b;
const sampleKakezanNumbers: SampleArrowFunction = (a, b) => a * b;
const sampleDivisionNumbers: SampleArrowFunction = (c, d) => c / d;
console.log(sampleAddNumbers(1, 3)); //--> 4
console.log(sampleKakezanNumbers(3, 3)); //--> 9
console.log(sampleDivisionNumbers(sampleKakezanNumbers(3, 3), 3)) // --> 3

void型

戻り値が存在しないことを示すために使用される型。

function voidFunctionSample(): void {
    console.log('戻り値なし!');
}
voidFunctionSample(); //--> 戻り値なし!

void型を指定した関数でreturnしようとするとエラー。

function voidFunctionWithReturn(): void {
    console.log('戻り値なし!');
    return 'これはエラーの関数'
}

voidを指定した関数の変数に、値を返却する関数を代入すると戻り値の型情報は無視される。
戻り値voidの関数型

type VoidFunctionType = () => void;

const returnStringFunction: VoidFunctionType = () => {
    return "文字列!"
}

const okResult = returnStringFunction(); //void型

okResultをstring型のように扱おうとするとエラーになる

console.log(okResult.toUpperCase());

関数のオーバーロード

二つの引数を合算して返却する関数addNumbers

function addNumbers(a, b) {
  return a + b;
}

この関数にそれぞれ引数を渡して実行すると・・・

引数に渡す値によって結果が異なる。

  • 数値→加算した結果を返却
  • 文字列→引数を連結して返却
  • 数値&文字列→文字列の連結(暗黙的型変換により、数値が文字列になる)
    上記のような関数あった場合、number | strintとして型を設定しtypeof演算子を使って型チェックをするのでも良いが、TypeScriptによる型推論でエラーとなる。

    このエラーの原因としては、「戻り値がnumber型の可能性があり、string型専用のメソッドをエラーなく呼び出せることができない」とTypeScriptが判断したから。
    ここで、同じ関数名に対してどのような関数なのかを表現する型定義をすることができる(呼び出しシグネチャ)
function addNumbers(a: number, b: number): number;
function addNumbers(a: string, b: string): string;
function addNumbers(a: number, b: string): string;
function addNumbers(a: string, b: number): string;

異なるパラメータと戻り値を持つ関数オーバーロードシグネチャを複数宣言することによって、TypeScriptは「addNumbersは複数の呼び出し方がある!」と判断する。

let resultAdd_5 = addNumbers("1", "2");//関数の呼び出し
console.log(resultAdd_5.includes("1"));//結果:true