TypeScript基礎

JavaScript

TypeScriptの型付の方法を参考動画を見ながらまとめました。

変数宣言

変数を宣言するときに型を指定する型注釈(type annotation)

let a:number = 20;
let b:string = 'hello';
let c:boolean = true;

ユニオン型

型注釈を複数つけたり、文字列リテラルで決まった値だけを指定することもできる

let age:string | number = 20;
age = "20さい";

let process:'proceed' | 'canceled' | 'pending';
process = 'proceed';
process = 'canceled';
process = 'pending';

配列

配列にも型注釈をつけることができる、文字や数値などを指定することなく[]で配列だけを指定すると空の配列を指定したことになる。

let prices:number[] = [100, 75, 42]

let fruit:string[] = ['apple', 'orange']

let emptyValues:[] = [];

配列の型注釈のユニオン型

let array:(string | boolean)[] = ["apple", true, "orange"];

●タプル型

異なる型を複数使えるが、要素の位置や個数は型に従わなければならない(同じ型が並ぶとなぜか配列にpushで要素を増やすことができてしまうのでreadonlyなどをつけた方がよい)

let person:[string, number] = ['john', 25]

let data: readonly [number, number, number] = [12, 34, 4555];

オブジェクト

オブジェクトは型付けたい値のキーを書いて型注釈をつけることができる。型をつけるときは「;」をつけて区切る

let car:{ brand:string; year:number } = {
    brand: 'toyota',
    year: 2020,
};

オブジェクトの配列も指定できる。値がないときはオプショナルプロパティを付けると良い

let book = {title:'book',cost: 20};
let pen = {title:'pen',cost: 10};
let notebook = {title:'notebook'};
let items:{title:string; cost?:number}[] = [book, pen, notebook];

readonly

readonlyをつけることで読み取り専用の値にできる

let people:{readonly name:string; age:number} = {
    name: "tarou",
    age: 23,
}
people.name = 'zirou'//×読み取り専用なので書き換えられない

関数

引数に型をつける(strictモードのときは型をつけないとコンパイルできない)

function sayHi(name:string){
    console.log(`Hello ${name}`)
}
sayHi('Psan');

返り値にも型をつける(返り値の型がなく、引数にanyを付けた場合、返り値などが全てanyになってしまう。予期しない型になるのでなるべく付けた方が良い)

function calculateDiscount(price:number):number{
    return price * 0.9;
}
console.log(calculateDiscount(198));

void型 何も返さないときはvoid

function logMessage(message:string):void{
    console.log(message);
}

logMessage("Hello");

オブジェクトが引数のときの処理

分割代入とインライン型注釈

引数がオブジェクトのとき、{id}のように分割代入と
({ id }: { id: number })のようにインラインで定義することもできます。

function createEmployee({id}:{id:number}):{id:number; isActive:boolean}{
  return {id,isActive: id%2===0};  
}

const first = createEmployee({id : 1});
const second = createEmployee({id : 2});

オブジェクトリテラル型の型注釈

オブジェクトを引数にとり、そのオブジェクトに型をつける

function createStudent(student:{id:number; name:string}):void{
    console.log(`welcome ${student.name.toUpperCase()}`)
}
const newStudent = {
    id: 5,
    name: 'anna'
};
createStudent(newStudent);

初期値を入れる場合

function processData(input:string | number, config:{reverse: boolean}={reverse:false}):string | number{
    if(typeof input === "number"){
        return input * input;
    }else{
        return config.reverse
            ? [...input].reverse().join("").toUpperCase()
            : input.toUpperCase();
    }
}

console.log(processData(5));
console.log(processData('hello'));
console.log(processData('hello', {reverse:true}));

オプショナルプロパティのフォールバック

引数などにオプショナルプロパティをつけると、その値がなかったときのフォールバックを定義しないとエラーになります。

論理OR演算子でのフォールバック(数値・ブールのときは非推奨

function caluculatePrice(price:number, discount?:number):number {
    return price - (discount || 0);
}

let priceAfterDiscount = caluculatePrice(100, 20);

上記はdiscountがなかったときのために論理OR演算子でショートサーキット評価を使用しています。ただし||は0やfalseもフォールバックの対象にしてしまうため数値やブール値の意図しない挙動を避けるためには使わない方が良い

null合体演算子でのフォールバック

function calculatePrice(price: number, discount?: number): number {
    return price - (discount ?? 0);
}

??はnullやundefinedのときだけフォールバック値を適用します。

デフォルト引数

引数に初期値やデフォルトの値を設定しておくことができます

function calculate(a:number, b:number = 0):number{
    return a - b;
}
console.log(calculate(100))

引数がなくても、デフォルト引数を設定していればその値が設定されます。

スプレッド構文

スプレッド構文も使える

function sum(message:string, ...numbers:number[]):string{
    let total = numbers.reduce((previous, current)=>{
        return previous + current;
    }, 0);
    return `${message}${total}`
}

let result = sum('the total is : ', 1, 2, 3, 4, 5)

型ガード(type guard)

ユニオン型など複数の型の引数をとる場合、値の型を実行時に判定して、その型に応じた処理を行うための構造です。TypeScriptでは、型ガードを使うことで、異なる型に応じた適切な処理を安全に行うことができます。(型を明確にしないとエラーが出ます)

function processInput(param:string | number) {
    if(typeof param === "number"){
        console.log(param*2);
    }else{
        console.log(param.toUpperCase())
    }
}

processInput("string");
processInput(20);

truthy, falsyな型ガード

truthy,falsyな値とは、論理値としてみたときにtrue, falseと返される値。これを型ガードに使い、処理をわけることができる

function printLength(str:string | null | undefined):void{
    if(str){
        console.log(str.length);
    }else {
        console.log("No string provided")
    }
}

printLength('hello');
printLength('');
printLength(null);

型エイリアス(Type Alias)

型に名前をつけて再利用できます。慣例的に大文字で始まります

type User = {id:number; name:string; isActive: boolean};

const john: User = {
    id:1,
    name: 'john',
    isActive: true,
};
const susan: User = {
    id:1,
    name: 'susan',
    isActive: false,
};

function createUser(user:User):User{
    console.log(`Hello there${user.name.toUpperCase()}!`)
    return user;
}

●型エイリアス拡張

type Book = {id:number; name:string; price:number};
type DiscountBook = Book & {discount:number};

const discounteBook:DiscountBook = {
    id:3,
    name:'Goblins',
    price: 25,
    discount:0.15,
};

インターフェース(interface)

型エイリアスとほぼ同じ。こっちの方がクラスっぽい。

interface Book{
    readonly isbn:number;
    title:string;
    author:string;
    genre?:string;
    //method
    printAuthor():void;
    printTitle(message:string):string;
}

const deepWork:Book = {
    isbn:123,
    title: 'deep work',
    author:'cal newport',
    genre:'self-help',
    printAuthor(){
        console.log(this.author);
    },
    printTitle(message){
        return `${this.title} ${message}`;
    }
}

deepWork.printAuthor();
console.log(deepWork.printTitle("が発売中!"));

アロー関数もメソッドとして使えるが、アロー関数のthisはグローバルオブジェクトを指すため、使わない方が良い

●インターフェース拡張

クラスみたいにextendsですでにあるプロパティに新規プロパティを追加できます

interface Person {
    name: string;
    getDetails():string;
}

interface Employee extends Person {
    employeeId:number;
}
const employee:Employee  ={
    name:"jane",
    employeeId:123,
    getDetails(){
        return  `Name: ${this.name}, EmployeeId:${this.employeeId}`
    }
}
console.log(employee.getDetails());

●インターフェースの型ガード

あるプロパティがあるかどうかでどのインターフェースか判定する

interface Person {
    name:string;
}
interface DogOwner extends Person {
    dogName:string;
}
interface Manager extends Person{
    managePeople():void;
    delegateTasks():void;
}

function isManager(obj:Person|DogOwner|Manager):boolean{
    return 'managePeople' in obj;
}
console.log(isManager(employee));//true or false

明示的に指定すると別の関数でもエラーがでない

function isManager(obj:Person|DogOwner|Manager):obj is Manager{
    return 'managePeople' in obj;
}
if(isManager(employee)){
    employee.delegateTasks();
}

enum

値を決めると勝手にインクリメントしてくれる。

enum ServerResponseStatus {
    Success, 
    Error,
}
console.log(ServerResponseStatus.Success)//0
console.log(ServerResponseStatus[1])//Error

キー:値を変えることもできる

enum ServerResponseStatus {
    Success = 200, 
    Error = 500,
}
console.log(ServerResponseStatus.Success)//200
console.log(ServerResponseStatus[500])//Error

型アサーション

as で型をつけることで明示することができます。一般的にTypeScriptが型をわかってないときに実装者が型を上書きしたり教えるために使います。

type Bird = {
    name:string;
};

let birdString = '{"name":"Eagle"}';//型はstring

let birdObject = JSON.parse(birdString);//このとき型はany

let bird = birdObject as Bird;//型がBirdになる

unknown

何でも変数に受け付けるが、anyと違って何か関数を実行しようとするときは型をチェックしてからでないとエラーがでる。

function runSomeCode():unknown{
    const random = Math.random();
    if(random < .5){
        throw new Error('there was error...');
    }else {
        throw 'some error'
    }
}

try{
    runSomeCode();
}catch(e){
    if(e instanceof Error){
        console.log(e.message);
    }else{
        console.log(e)
    }
}

never

どんな型の値を入れてもエラーが出る

例えば下記のように二つしか値が入ってないタイプなのに、三つ目を指定しようとするとVSCodeなどがneverという型だと指摘してくる。

type Theme = 'light' | 'dark';

function checkTheme(theme:Theme):void{
    if(theme === 'light'){
        console.log('light theme');
        return;
    }
    if(theme === 'dark'){
        console.log('dark theme');
        return;
    }
    if(theme === )
}