Open18

TypeScript勉強日記

ゆるふうぇいとゆるふうぇいと

はじめに

日々のTypeScriptの勉強内容を、日記形式でつらつら書き連ねていく。
学んだことがどんなに軽微であっても、一日5分であっても、継続してやる。
(ただし無理はしない)

参考書籍はいわゆるブルーベリー本

プロを目指す人のためのTypeScript入門 安全なコードの書き方から高度な型の使い方まで (Software Design plus)
https://amzn.asia/d/7xUhWWA

ゆるふうぇいとゆるふうぇいと

2024/12/2

環境構築

Node.jsのversion

いつインストールしたか忘れたけど、既にインストール済だった
v20.16.0

エディタ

Visual Studio Codeを利用する

packeage.jsonの作成

以下コマンドを実行

npm init --yes

そのあと、"type":"module"を追加
最終的には以下となる

{
  "name": "tsblueberry",
  "version": "1.0.0",
  "description": "TypeScriptの勉強用",
  "main": "index.js",
  "type": "module",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

TypeScriptのインストール

以下コマンドを実行

 npm install --save-dev typescript @types/node

tsconfig.jsonの準備

tsconfig.jsonは、TypeScriptコンパイラに対する設定を記述したファイルのこと
以下コマンドを実行。その後細かい調整しているがそこは本に書かれている通りのため、割愛。。

npx tsc --init

初めてのTypeScriptプログラム

Hello,world!

どんな言語もまずはここから。

srcフォルダを作成し、その中にindex.tsを作成

index.tsの中身はこんな感じ

const message: string = "Hello, world!";

console.log(message);

以下コマンドを実行して、コンパイル
ちなみに、rscはType Script Compilerの略らしい

npx tsc

その結果、distフォルダの配下にindex.jsが生成される。
中身はこんな感じ。TypeScriptから、javaScriptに変換されている

const message = "Hello, world!";

console.log(message);

node.jsを用いて実行する

ゆるふうぇいとゆるふうぇいと

2024/12/3

基本的な文法

constとletの違い

再代入 宣言時の値代入
let 可能 任意
const 不可 必須
// letは再代入可能,constは不可
const cstGreeting: string = "Hello";
const cstTarget: string = "world";
cstGreeting = "Hello2"; // 不可

let letGreeting: string  = "Hello";
let letTarget: string = "world";

letGreeting = "Hello2";

console.log(letGreeting + letTarget);

コンパイルエラー

constは、イメージとしてはJavaでいうところのfinal 変数
なので、基本的にconstを使う方が安全であると言えそう。

ゆるふうぇいとゆるふうぇいと

2024/12/6

早速三日さぼった

数値型について

TypeScriptにおいて、数値はnumber型であつかう。
number型は、整数と小数を区別しない

const width1 = 5;
const width2 = 8;
const height:number = 3;
const area = (width1 + width2) * height / 2;

console.log(area);

上記の結果、 19.5が表示される。

TypeScriptの主流はnumber型
最近になってBigIntが追加されたらしい。
JavaでいうところのDECIMALとかはない

任意精度整数:BigInt

BigInt = 任意精度の整数

BingIntのリテラルは、123nのように、整数の後にnを書くリテラルとなっている。

文字列中のエスケープシーケンス

バックスラッシュで始める。

3大プリミティブ

数値、文字列、真偽値

null と undefined

nullとundefinedもプリミティブ型である。
どちらも「データが無い」ことを示す。
使い分けは難しいが、undefinedの方がよさそう(サポートが厚い、らしい)

プリミティブ型同士の変換

暗黙的な変換

   // 結果、"11000"と表示される
    console.log(l"1" + 1000);

文字列との結合だと解釈され、1000が暗黙的に文字列"1000"に変換される

明示的な変換

Number関数で、文字列を数値に変換する

    // "1"が、数値の1に変換され、結果1001が表示される
    console.log(Number("1") + 1000);

数値に変換できない文字列をNumber関数に設定すると、結果NaNになる
NaN + 1000 はNanなので、最終的に表示される結果もNaNになる

    // 結果: NaN
    console.log(Number("a") + 1000);
ゆるふうぇいとゆるふうぇいと

2024/12/8

真偽値への変換

0,0n,Nan,"",null,undefinedはFalse。それ以外はすべてTrueになる

// 真偽値への変換
// true
console.log(Boolean("hoge"));
console.log(Boolean(1n));

// false
console.log(Boolean(0n));
console.log(Boolean(""));
console.log(Boolean(NaN));
console.log(Boolean(null));
console.log(Boolean(undefined));
console.log(Boolean(0));

演算子

四則演算

整数・小数を区別しないnumber型の場合は、小数が表示される
一方で、整数しか扱わないBigInt型は、切り捨てられる。
このあたりは用途に応じて使い分けられそう

console.log(1 /2); // 0.5
console.log(1n / 2n); // 0n

number型とBigInt型を混ぜて使うとコンパイルエラーになる

console.log(1 / 2n); // コンパイルエラー

BigInt型は、変数にすると四則演算できなくなるっぽい

// できない
const b1:BigInt = 2n;
console.log(b1 + 3n);
console.log(b1 - 3n);
console.log(b1 / 3n);
console.log(b1 * 3n);

// できる
const b2:BigInt = 2n * 3n;
console.log(b2);
console.log(2n + 3n);
console.log(2n - 3n);
console.log(2n / 3n);
console.log(2n * 3n);

比較演算子

<,>,<=,>= は割愛。まぁそのまんま

一致判定

一致・不一致を判定する演算子は以下の4つ

  • ==
  • !=
  • ===
  • !==

一致・不一致の比較は、基本的に '===' または '!==' を使うこと
'=='、および'!='は暗黙的な型変換をしてしまう。以下具体例
(おそらく、厳密には上記説明では不十分な気もするが、とりあえず==と!=は使わないが吉となりそう)

    console.log(str == 2); // true
    console.log(str === 2) // false

例外的に nullとの比較であれば== を使ってもよさそう

例外的に、nullとの比較であれば == を使ってもよさそう。
x == null は、「xがnull、またはundefined」の場合にtrueとなるので、
両者を厳密に差別化したいなどなければ、こっちの比較の方が楽ではある
(個人的には、x === null || x === undefined と書いてもいいと思う。)

import { createInterface } from "readline";

const rl = createInterface({
    input: process.stdin,
    output: process.stdout
});

rl.question('文字列を入力してください:',(inputstr) => {

    let tmp;

    if(inputstr === "null"){
        tmp = null;
    } else if (inputstr === "undefined"){
        tmp = undefined;
    }

    if(tmp == null){
        console.log("null または undefined")
    }

    if(tmp === undefined){
        console.log("undefined!!!")
    }
    
    if(tmp === null){
        console.log("null!!!")
    }

    rl.close();

});

NaNとの比較

NaNとの比較は、必ずfalseになる
以下、全部false

    // falseになるよ
    const x:number = NaN;
    const y:number = NaN;
    console.log(x < 10);
    console.log(x == 10);
    console.log(x > 10);
    console.log(x === y);
    console.log(x == y);

評価

&& と || の特殊な使い方

基本的には、それぞれいわるゆ論理積と論理和だが、以下のような特性も持つ

評価式 xの結果 戻り値
x && y true y
x && y false x
x || y true x
x || y false y

上記の特性を生かし、 x || y は、デフォルト値の設定に使われる、らしい。
イメージ

rl.question('名前を入力してください:',(inputstr) => {

    const name:String = inputstr || "デフォルト";

    console.log(`あなたの名前は「${name}」です`);

    rl.close();

});

実行結果

値が null または undefined であることを判定する ??

x ?? y は、xがnullまたはundefinedの時のみ、yを返す

|| と同様に、デフォルト値を利用したい場合などに利用できる。
(|| は 0 や 空文字もfalse)

条件演算子

いわゆる三項演算子

条件式 ? 真のときの式 : 偽のときの式

条件式は、boolean型でなくてもよい
条件式に設定されたものを、真偽値に変換したものを利用する。
例えば以下の場合、number型のnumの値に応じて、結果が変わる

// 数値型のnumを真偽値に変換した結果、true/falseで結果が変わる
num ? console.log(`{num}は0およびNaNではない`) : console.log(`${num}は0またはNaNである`);

ゆるふうぇいとゆるふうぇいと

2024/12/10

オブジェクトの扱い

以下のように、オブジェクトとプロパティを定義できる

const obj = {
    foo: 123,
    bar: "Hello,world!",
};

console.log(obj.foo);
console.log(obj.bar);

// []を利用してプロパティを取得する
console.log(obj["foo"]);
console.log(obj["bar"]);

スプレッド構文

別のオブジェクトのプロパティをコピーして、利用する
以下例では、obj2の中に、objのプロパティがコピーされている

const obj = {
    foo: 123,
    bar: "Hello,world!",
};

// スプレッド構文を利用した例
const obj2 = {
    baz: 123,
    ...obj, // objの内容を展開
};

// スプレッド構文は、オブジェクトのコピーを作成する
console.log(obj.bar); // Hello,world!
obj.bar = "Goodbye,world!";

// obj.barの値が変更されているが、obj2.barは変更されていない
console.log(obj2.bar) // Hello,world!

オブジェクトの比較

オブジェクト同士を '===' で比較した場合、
同一のオブジェクトであればtrue、異なるオブジェクトであればfalseになる
また、オブジェクトと非オブジェクトの比較は、常にfalseになる…らしいが、そもそもコンパイルエラーになる

// オブジェクトのサンプル
const sample1 = { key: 'value' };
const sample2 = { key: 'value' };

console.log(sample1 === sample2); // false

// オブジェクトのコピー
const copy1 = sample1;

console.log(copy1 === sample1); // true

// コンパイルエラー
console.log(sample1 === numSample); // false

オブジェクトの型安全性

オブジェクトのプロパティに対し、
宣言されている型と異なる型の値をセットしようとすると、エラーとなる

const obj3:{
    foo: number,
    bar: string,
} = {
    foo: "123", // コンパイルエラー
    bar: "Hello,world!",
}

オブジェクトのプロパティに対し、値を設定しない場合もコンパイルエラーとなる
ただし、オプショナルの場合は別(オプショナルは後述)

const obj3:{
    foo: number,
    bar: string,
} = {
    foo: 123,
    // コンパイルエラー
}

type文で型に別名を付ける

以下構文で、型名を定義出来る

type 型名 = 型;

使い方例

type FooBarObj = {
    foo: number;
    bar: string;
};

const foobarobj: FooBarObj = {
    foo: 123,
    bar: "Hello,world!",
}

stringやnumberといった型に対しても行うことができる

ゆるふうぇいとゆるふうぇいと

2024/12/12

オプショナルなプロパティ

オプショナルなプロパティ = 値の設定が必須ではないプロパティ
プロパティ名の後ろに?を付加する
未設定のプロパティは、undefinedになる

type MyObj = {
    foo: number;
    bar?: string;
}

// bar?はオプショナルなプロパティ
const myObj: MyObj = { foo: 123 };

console.log(myObj.bar); // undefined

読み取り専用プロパティ

プロパティ名の前にreadonlyを付与することで、読み取り専用になる

// readonlyプロパティ
type MyObj2 = {
    readonly foo: number;
}

const myobj2: MyObj2 = { foo: 123};

// コンパイルエラー
myobj2.foo = 999;

typeofキーワード

typeofキーワードを利用して、変数の型を得る

const num2:number = 123;

type T = typeof num2;
const foo:T = 123;
ゆるふうぇいとゆるふうぇいと

2024/12/6

大分さぼってしまった。

部分型関係

Javaでいう所の継承に少し考え方が似ている
以下例において、型FooBarBazは、型FooBarの部分型である、と表現できる

// 部分型のサンプル
type FooBar = {
    foo: number;
    bar: string;
}

// FooBar型を部分型として利用
type FooBarBaz = {
    foo: number;
    bar: string;
    baz: boolean;
}

// FooBar型に対し、その部分型であるFooBarBaz型を代入することが可能
const obj4: FooBar = obj3;

FooBarBaz、がFooBarを継承しているようなイメージ
FooBarBaz型は、FooBar型ととらえることもできる

部分型の条件

型Sが型Tの部分型、となる条件
以下2つの条件が満たされていること
1.Tが持つプロパティが既にSにも存在する
2.条件1の各プロパティについて、Sにおけるそのプロパティの型はTにおけるプロパティの型の部分型である

// Humanは、Animalの部分型
type Animal = { 
   age: number;
}

type Human = {
    age: number;
    name: string;
}
ゆるふうぇいとゆるふうぇいと

2024/12/7

最近眠くてすぐ寝落ちしてしまう。進捗がさすがに悪すぎる。
コーヒー飲みます。

プロパティの包含関係による部分型関係の発生

以下のAnimalFamilyと、HumanFamilyは同一に扱える

// 動物の家族
type AnimalFamiliy = {
    familyName: string;
    mother: Animal;
    father: Animal;
    child: Animal;
}

// 人間の家族
type HumanFamily = {
    familyName: string;
    mother: Human;
    father: Human;
    child: Human;
}

const human : HumanFamily = {
    familyName: "Smith",
    mother: {
        age: 30,
        name: "Alice"
    },
    father: {
        age: 35,
        name: "Bob"
    },
    child: {
        age: 5,
        name: "Tom"
    }
};

// HumanFamily型をAnimalFamily型に代入することが可能
const family: AnimalFamiliy = human;

余剰プロパティに対するエラー

型に存在しないプロパティを指定すると、エラーになる。
下記例でいえば、User型はtelNumberを持っていないので、エラーになる

// ユーザー型
type User = {
    name: string;
    age: number;
}

const u: User = {
    name: "Taro",
    age: 25,
    telNumber: "09012345678" // エラー。User型はtelNumberプロパティを持たない
}

型推論を利用し、以下の内容であればエラーにならずにUser型として扱える

// ユーザー型
type User = {
    name: string;
    age: number;
}

const u  = {
    name: "Taro",
    age: 25,
    telNumber: "090-1234-5678"
}

const usr:User = u;

型引数

型を定義するときに、パラメータを利用できる。
呼び出し側で、パラメータに値を設定する。

ジェネリクスっぽい

type User<T> = {
    name: string;
    child: T;
}

const humanUsr: User<Human> = {
    name: "Smith",
    child: {
        age: 5,
        name: "Tom"
    }
};
ゆるふうぇいとゆるふうぇいと

2024/12/22 6:00

色々忙しくてさぼりまくり。今日は沢山すすめたい
また、スクラップのタイトルとして、勉強の開始時刻を記録することにした。
1日のうちに複数回時間を分けて勉強した時とかで分けたい

勉強はやっぱり朝の方がはかどるね。早起きしましょう。

型引数

続き。
型引数は、複数あってもよいし、
また前回のように"T"みたいな一文字じゃなくてもいい。
例えば以下のように。

// Parent,Childという複数の型引数を持つ、Family型を定義
type Family<Parent, Child> = {
    parent: Parent;
    child: Child;
}

const familyObj : Family<string,number> = {
    parent: "Smith",
    child: 5
}

後はこんな感じで

type Family<Parent, Child> = {
    parent: Parent;
    child: Child;
}

// Parent型の定義
type Parent = {
    name: string;
    age: number;
}

// Child型の定義
type Child = {
    name: string;
    age: number;
    parent: Parent;
}

const myParent: Parent = {
    name: "Smith",
    age: 40
}

const myChild: Child = {
    name: "Tom",
    age: 5,
    parent: myParent
}

const myFamily: Family<Parent, Child> = {
    parent: myParent,
    child: myChild
}

部分型関係による型引数の制約

extendsを付与することで、部分型関係である必要がある、という制約を付けられる。
Javaでいうところの境界ワイルドカードに近いかも。

// Parent、ChildはHasNameの部分型である必要がある
type Family<Parent extends HasName, Child extends HasName> = {
    parent: Parent;
    child: Child;
};

// myFamilyの定義
const myFamily: Family<Parent, Child> = {
    // nameがないとコンパイルエラー
    parent: {
        name: "Smith",
        age: 40
    },
    
    // nameがないとコンパイルエラー
    child: {
        name: "Tom",
        age: 5,
        parent: {
            name: "Smith",
            age: 40
        }
    }
};

オプショナルな型引数

デフォルトの型引数を指定できる

// オプショナルな型引数
type Animal = {
    name: string;
}


// 指定された型引数がない場合はAnimal型が使用される
type AnimalFamily<Parent = Animal,Child = Animal> = {
    parent: Parent;
    child: Child;
}

const stringFamily: AnimalFamily<string,string> = {
    parent: "Smith",
    child: "Tom"
}

const defaultFamily: AnimalFamily = {
    parent: {
        name: "Smith"
    },
    child: {
        name: "Tom"
    }
}

部分型制約との併用もできる

// 部分型制約との併用も可能
type AnimalFamily<Parent extends HasName,Child extends HasName = Animal> = {
    parent: Parent;
    child: Child;
}
ゆるふうぇいとゆるふうぇいと

2024/12/22 13:00

タプル型

要素数が固定されている配列のこと
異なる型を与えられるらしいが、使いどころがいまいちわからないが、
定数配列、みたいな感じで使えるのかも


let tuple: [string,number,boolean] = ["hoge",10,true];

const str = tuple[0];
const num = tuple[1];
const bool = tuple[2];

分割代入

オブジェクトの分割代入

オブジェクトに設定されたプロパティを、お手軽に変数に代入する手法
注意点として、変数側に型注釈を入れることが出来ないため、
常に型推論を用いて設定されることになる


const obj = {
    foo: 123,
    bar: "Hello,world!",
};

// オブジェクトの分割代入
// obj.fooをfoo変数に、obj.barをbar変数に代入している
// プロパティと同名の変数に入れる場合
const { foo,bar } = obj;
console.log(foo);
console.log(bar);

// プロパティ名と変数名を変える場合
const { foo: hoge, bar: fuga } = obj;

console.log(hoge);
console.log(fuga);

// ネストしたパターンへの分割代入
const nested = {
    nfoo: "FOO!!",
    nvar:{
        nbar: "BAR!!",
    }
}

const {nfoo,nvar:{nbar}} = nested;

console.log(nfoo);
console.log(nbar);


// 配列との組み合わせ
const objArr = {
    arr: [1,2,3],
}

const {arr:[first]} = objArr;
console.log(first);

分割代入にデフォルト値を設定する

オプショナルなプロパティを、分割代入を用いて変数に設定する際に、デフォルト値を設定できる。
オプショナルなプロパティが未設定(undefined)の場合、デフォルト値が用いられる
nullの場合はデフォルト値は用いられないことに注意

// 分割代入において、デフォルト値を設定できる
type OptObj = { optFoo?:number};

// 任意項目未設定
const optObj1: OptObj = {};

// 任意項目設定
const optObj2: OptObj = { optFoo: 123};

// optFooが未設定の場合、500が代入される
const {optFoo = 500} = optObj1;
const {optFoo:optFoo2 = 500} = optObj2;

// null と undefinedの挙動確認
type OptObj2 = { optFoo?:any};
const optObj3: OptObj2 = {optFoo :null};
const optObj4: OptObj2 = {optFoo :undefined};

const {optFoo:optFoo3 = 500} = optObj3;
const {optFoo:optFoo4 = 500} = optObj4;

// nullが表示される
console.log(optFoo3);

// 500が表示される
console.log(optFoo4);

ゆるふうぇいとゆるふうぇいと

2024/12/22 23:00

restパターン

オブジェクトの残りを取得する。例は以下

const { foo,...resetObj} = obj;

分割代入されたオブジェクトの残りのプロパティをすべて持つ、新たなオブジェクトが代入される

// restパターン
const obj = {
    foo: 123,
    bar: "string",
    baz: false,
};

const { foo, ...restObj } = obj;

console.log(foo); // 123
console.log(restObj); // { bar: 'string', baz: false }

様々なオブジェクト

Date型

日付を表す。作成した時点での現在日時を保持する。

// Date型の値はnew Date()で生成する
const today = new Date();
console.log(today);
console.log(today.getFullYear()); // 現在の年
console.log(today.getMonth()); // 現在の月
console.log(today.getDate()); // 現在の日
console.log(today.getDay()); // 現在の曜日
console.log(today.getHours()); // 現在の時
console.log(today.getMinutes()); // 現在の分
console.log(today.getSeconds()); // 現在の秒
console.log(today.getMilliseconds()); // 現在のミリ秒
console.log(today.getTime()); // 1970年1月1日からのミリ秒数

正規表現オブジェクト

TypeScriptでも当然正規表現が扱える。
他の言語と仕様は大体同じ。
オプションを利用すると、「大文字・小文字を区別しない」などができⅧる

const r = /ab+c/;

console.log(r.test("abbbbc")); // true

const r2 = /^abc/;
console.log(r2.test("abc")); // true
console.log(r2.test("defabc")); // false

以下のようなメソッドも利用可能
・repalce
・文字列の中で、正規表現にマッチする部分を置換する
・match

ゆるふうぇいとゆるふうぇいと

2024/12/27 23:58

眠い

Map

キー・値のペア
setメソッドで値を追加し、getメソッドで取得する
保存されているアイテムがなかった場合、undefinedになる

const map: Map<string,number> = new Map();
map.set("foo",123);
map.set("bar",456);

console.log(map.get("foo")); // 123
console.log(map.get("bar")); // 456
console.log(map.get("baz")); // undefined

WeakMap

通常のMapと以下がことなる
・キーにオブジェクトのみをもつ
・列挙系のメソッド(keys,values,entires)が存在しない

// Person型を定義
type Person = {
    name: string;
    age: number;
    address: string;
}

// WeakMap型
const wmap: WeakMap<Person,number> = new WeakMap();
const taro: Person = {
    name: "Taro",
    age: 25,
    address: "Tokyo",
}

wmap.set(taro,123);

console.log(wmap.get(taro)); // 123

関数

関数は以下のような形で宣言する

function 関数名(引数リスト):返り値の型{中身}

サンプル

// 関数名:range
// 引数:min,max
// 返り値:number[]
function range(min: number,max: number): number[]{
    const result = [];
    for(let i = min; i <= max; i++){
        result.push(i);
    }
    return result;
}
ゆるふうぇいとゆるふうぇいと

2024/12/30 15:48

関数(続き)

関数式

関数を式として定義し、変数に代入する
クラスに定義したメソッドを、インスタンスから呼び出すようなイメージ
ただ、Javaでいうところの関数型インタフェースのほうがより近い気がする

構文は以下の通り

function (引数): 返り値の型 {中身}
type Human = {
  height: number;
  weight: number;
};

// 関数式を定義し、変数calcBMIに代入
const calcBMI = function (human: Human): number {
  return human.weight / human.height ** 2;
};

const uhyo: Human = { height: 1.84, weight: 72 };
console.log(calcBMI(uhyo));

引数に対して、分割代入を用いることも可能

// 分割代入
const calcBMI2 = function ({ height, weight }: Human): number {
  return weight / height ** 2;
};

console.log(calcBMI2(uhyo));

アロー関数式

アロー関数も可能
構文は以下の通り

function (引数): 返り値の型 => {中身}
const calcBMI = ({ height, weight }: Human): number => {
  return weight / height ** 2;
};

アロー関数式は、以下のようにfunctionを省略することが出来る

(引数): 返り値の型 => 式

正し、省略形出かけるのは式が一つの場合のみ

const calcBMI3 = ({ height, weight }: Human): number => weight / height ** 2;
ゆるふうぇいとゆるふうぇいと

2024/12/30 21:54

可変長引数

関数が任意の数の引数を受けとれるようにするこお
rest引数構文を用いる

...引数名: 型

関数宣言の引数リストの最後で1回だけ使用できる
rest引数は必ず配列型orタプル型である必要がある

const concat = (...args: string[]): string => {
  let result = "";
  for (const str of args) {
    result = result + str;
  }
  return result;
};

rest引数は、通常の引数と併用することが可能

const sum2 = (base: number, ...arg: number[]): number => {
  let result = base * 1000;
  for (const num of arg) {
    result = result + num;
  }
  return result;
};

console.log(sum2(10, 20, 30, 40));

スプレッド構文

関数の呼び出し側でrest構文を用いて配列を渡すことで、
「その配列がもつ値を、順番にすべて関数に渡す」というように利用できる

const sum = (...args: number[]): number => {
  let result = 0;
  for (const arg of args) {
    result += arg;
  }

  return result;
};

const nums = [1, 2, 3, 4, 5];

// スプレッド構文
console.log(sum(...nums));

オプショナル引数

呼び出し側で設定してもしなくてもよい、という任意の項目となる引数
また、デフォルト値を設定することもできる
渡されなかった場合、デフォルト値が設定されていればその値となり、
デフォルト値が設定されていない場合はundefinedとなる

// オプショナル引数
const toLowerOrUpper = (str: string, upper?: boolean): string => {
  // 未設定の場合、upeperはundefinedとなる
  if (upper) {
    return str.toLowerCase();
  } else {
    return str.toUpperCase();
  }
};

// オプショナル引数upperは、設定しなくてもしてもよい
console.log(toLowerOrUpper("Hello"));
console.log(toLowerOrUpper("Hello", true));

デフォルト値を設定する場合は以下になる

// デフォルト値を設定する場合
const toLowerOrUpper = (str: string, upper: boolean = false): string => {
  if (upper) {
    return str.toLowerCase();
  } else {
    return str.toUpperCase();
  }
};

コールバック関数

「関数の引数として、関数を渡す」

type User = { name: string; age: number };
const users: User[] = [
  { name: "Uhyo", age: 26 },
  { name: "John Smith", age: 15 },
];

// 渡されたUser型の変数から、nameを取得して返却
const getName = (u: User): string => u.name;

// コールバック関数として、getNameを指定
// map関数
const names = users.map(getName);
const names2 = users.map((u: User): string => u.name);

console.log(names);

関数の型

ゆるふうぇいとゆるふうぇいと

2025/1/2~3

あけましておめでとうございます。
勉強はしていたがzennにメモするのをさぼっていたためまとめてメモ

Classについて

Javaで学んでいたこととほとんど同じなので、割とすっと理解はできたと思う

通常のクラス宣言

プロパティにreadonlyを設定することが出来る。
readonlyを設定した場合、外部からの上書きは不可となる。
ただし、後述するコンストラクタにおいては、設定が可能

class User {
  name: string = "";
  optname?: string;
  readonly roname: string = "";
  age: number = 0;
}

const uhyo = new User();
console.log(uhyo.name);
console.log(uhyo.age);
console.log(uhyo.optname); // undefined

コンストラクタ

コンストラクタを定義することも当然できる

class User {
  name: string;
  age: number;
  readonly roage: number;

  // コンストラクタの定義
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;

    // コンストラクタでは、readonly属性にも値を設定できる
    this.roage = age;
  }
  isAdult(): boolean {
    return this.age >= 20;
  }
}

アクセス修飾子

public、protected、privateそれぞれ定義できる
アクセス制限の考え方も基本的にはJavaと同じだが、
package privateの考え方はなさそう

・public:誰でも(デフォルト)
・protected:自分自身 or 継承クラス
・private:自分自身のみ

コンストラクタ引数を用いたプロパティ宣言

コンストラクタ引数を用いることで、
インスタンス生成時(コンストラクタ発動時)に、設定が必須となるプロパティの記載を簡潔にできる。
百聞は一見に如かず。

class User {
  constructor(public name: string, private age: number) {}
}

// 上記は、以下と同じ働きをする
class User {
  name: string;
  private age: number;

  // コンストラクタの定義
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

静的初期化ブロック

クラス宣言の中にstatic{...}という構文を書ける機能

ゆるふうぇいとゆるふうぇいと

2025/1/4 5:15~

久しぶりに早起きに成功するなど

クラス(続き)

型引数

クラスに型引数を持たせることが出来る
インスタンスを生成する際に型引数を利用することで、
クラスの持つ特定の属性の方を、インスタンスの生成側で指定できるイメージ

export {};

class User<T> {
  name: string;
  #age: number;
  readonly data: T;

  constructor(name: string, age: number, data: T) {
    this.name = name;
    this.#age = age;
    this.data = data;
  }
}

const uhyo = new User<string>("uhyo", 20, "追加データ");

// 型推論
const john = new User("John Smith", 15, { num: 123 });

クラスの型

クラス宣言は型を作ること、というお話
詳細は割愛するが、以下の内容は印象的
・変数と型は異なる名前空間に属する
・クラス宣言は、両方の名前空間にデータを生成するイメージ

instanceof

特定のインスタンスが対象のクラスから生成されたインスタンスであるか否かを判定する演算子
あくまでも、new 演算子により生成されたものかどうかを判定するため、
型が一致しているだけでnew 演算子により生成されていないものはfalseになる

const uhyo = new User();

// true
console.log(uhyo instanceof User);

// false
console.log({} instanceof User);

const john: User = {
  name: "John Smith",
  age: 15,
};

// false
// User型の変数というだけで、Userのインスタンスではない
// (new 演算子により生成されたものではない)
console.log(john instanceof User);

クラスの継承

クラス同様、Javaが持つ考え方とほぼ同じ認識
overrideなどもある。

class User {
  name: string;
  #age: number;
  #border: number = 20;

  constructor(name: string, age: number) {
    this.name = name;
    this.#age = age;
  }

  public isAdult(): boolean {
    return this.#age >= this.getBorder();
  }

  protected getBorder(): number {
    return this.#border;
  }
}

class PremiumUser extends User {
  #border: number = 10;

  public override isAdult(): boolean {
    return super.isAdult();
  }

  protected override getBorder(): number {
    return this.#border;
  }
}
ゆるふうぇいとゆるふうぇいと

2025/1/4 14:00

implements

キーワードによる型チェックとして利用できる
インタフェース・・・の考え方でいいと思う
以下のサンプルの場合、Userクラスが、name属性を持つHasNameに対しimplementsしているため、Userクラスはname属性を持つ必要がある、ということ

type HasName = { name: string };

class User implements HasName {
  name: string; // HasNameをimplementsしているのでnameプロパティが必要
  #age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.#age = age;
  }

  isAdult(): boolean {
    return this.#age >= 20;
  }

  setAge(newAge: number) {
    this.#age = newAge;
  }
}

アロー関数を用いたthis

アロー関数内で用いるthisは他の通常のthisと異なり、外側の関数からthisを受け継ぐ。

class User {
  name: string;
  #age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.#age = age;
  }

  public isAdult(): boolean {
    return this.#age >= 20;
  }

  public filterOlder(users: readonly User[]): User[] {
    // アロー関数内のthisは、外側の関数から受け継がれる
    // filterOlderメソッドのthisがUserインスタンスを指す
    return users.filter((u) => u.#age > this.#age);
  }
}

applyメソッドを用いたthisの変更

console.log(uhyo.isAdult.apply(john)); // false

bindメソッドを用いたthisの固定

const boundIsAdult = uhyo.isAdult.bind(uhyo);

例外処理

java同様に、try~catch~finallyが使える

export {};

try {
  console.log("エラーを発生させます");
  throwError();
  console.log("エラーを発生させました");
} catch (error) {
  console.log("エラーが発生しました");
  console.log(error);
} finally {
  console.log("完了しました");
}

// エラーを発生させる
function throwError() {
  throw new Error("This is an error");
}