Open38

Reactを学ぶ

ワッキーワッキー

viteを使ってreact開発環境を整える

モダンなフロントエンド開発を爆速で行えるビルドツール+開発サーバー
npm create vite@latest hello-world -- --template=react-ts
ルーティングやssr、apiサーバーなど自分で構築する必要がある
--template=react-tsは使いたいプロジェクトのテンプレートを指定している
色んなテンプレートがある約16種類

npm run dev localhost 5173
ポートの変え方npm run dev -- --port=3000

ワッキーワッキー

viteレンダリング構造

具体的な流れ
index.html(ViteやCRAで最初からある)

<body>
  <div id="root"></div> <!-- ここは空 -->
</body>
main.tsx(またはindex.tsx)

createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <App />
  </StrictMode>
);
実行後のDOM(ブラウザ側)

<body>
  <div id="root">
    <!-- Reactがレンダリングした内容 -->
    <div>
      <h1>Hello World</h1>
      ...
    </div>
  </div>
</body>
rootの空のdivの子要素として渡している
ワッキーワッキー

StrictModeタグとは

アプリの問題点を見つけてくれる

ワッキーワッキー

コンポーネント

例App 大文字で始まる決まり
レゴブロックのように組み合わせてアプリのuiを構築するためのパーツ

Reactではコンポーネントの実装は関数またはクラスで定義する

viteデフォルトでは関数コンポーネントで定義されている

HTMLに見えるが実はJSなJSX

main.tsx App.tsx tsベースのjsx コンパイルを経て最終的にjsコードに変換される

App.tsxのコードを書き換えると即座に反映されるHMR

viteが実現してくれている

ワッキーワッキー

プロジェクトファイル説明 vite + npm + react + tsの場合

src/ アプリケーションのソースコードが置かれる
node_modeles/ アプリケーションに必要なnpmパッケージが保存されている
public/ 公開用のアセットファイルを置く、静的ファイルを置く画像とか
package.json なにを使うかのカタログ
package.lock.json 何が実際に入ったかの完全記録
tsconfig.json tsをコンパイルするための設定ファイル
tsconfig.app.json アプリケーションコード用のコンパイラオプションを記述 ブラウザ用
tsconfig.node.json ビルドツールの設定ファイルをコンパイルするためのオプション記述 nodejs用
eslint.config.js コード品質を保つのに使う、ルール追加してエラー出してくれる
vite.config.ts Viteの設定ファイル
.gitignore Gitリポジトリに含めないものリスト
ワッキーワッキー

npm 以外のパッケージマネージャ yarn pnpm

npmは2010年
yarbはMetaにより2016年
pnpmは2017年

ワッキーワッキー

npm スクリプト

package.jsonのscriptエントリ

  "scripts": {
    "dev": "vite",//開発サーバー立ち上げ
    "build": "tsc -b && vite build",//ビルド実行
    "lint": "eslint .",//リンター実行
    "preview": "vite preview"
  }

予約ワード

start 本番環境サーバー立ち上げ登録用
restart アプリサーバー再起動コマンド登録用
stop アプリサーバー停止コマンド登録用
test テスト実行開始コマンドの登録用

installを実行した時に
preinstallはインストール前に実行される
postinstallはインストール後に実行される

ワッキーワッキー

変数宣言

varはもう使わねー
var 再宣言可能 再代入可能
let 再宣言不可 再代入可能
const 再宣言不可 再代入不可

letはミュータブル可変
constはイミュータブル不変

変数の参照が巻き上げられる

a = 100
console.log(a)
var a 
実行結果
100

変数の宣言前に代入できてしまう
letやconstならエラーになるがvarの場合では許される
これを巻き上げという(Hoisting)
ミスリードに繋がる

スコープ単位が関数

var n = 0
if (true){
  var n = 50
  var m = 100
  console.log(n)
  console.log(m)
}

console.log(n)
console.log(m)

実行結果
50
50
100

if内部のmが外側から参照されてしまう
varで定義された変数のスコープは関数単位のスコープで
if文のような制御構文のブロックをすり抜けしまう
letやconstはブロックスコープなのですり抜けない

ワッキーワッキー

JSのデータ型

jsは動的型付け言語で宣言した型と別のデータ型を入れ直せる

let num = 100
num = "foo"

tsは静的型付け言語でデータ型が異なる再宣言は不可

let num = 100
num = "foo"
エラーになる
ワッキーワッキー

プリミティブ型とオブジェクト型

プリミティブ型はオブジェクト型ではない、インスタンスメソッドを持たないデータであるということ

7種類のプリミティブ型

Boolean true false 真偽値
Nunbar 数値 整数も少数も同じ
BigInt 長整数型 Numbar型では扱い切れない大きな数値を扱うためのデータ型
String 文字列
Symbol シンボル値という固有の識別子を表現する値Symbol()関数を呼び出すことで動的に生成されるが同じシンボル値を後から作成できない
Null 何のデータもない状態を明示する
Undefined 返り値のデータがない時やオブジェクト内の存在しないプロパティへのアクセスに割り当てられる
BigintとSymbol以外の5個をよく使う

Falsy

false 0 NaN ""空文字列 null undefined

NaNになるもの

0の割り算、NaNを含んだ演算
その他無効な演算

>Math.sqrt(-1) //-1の平方根
>Infinity * 0 // 無限大と0の乗算
>parseInt("foo") //数字以外の文字列を数値としてパース
これらすべてNaN

NaNかどうかの判定するNumber.isNaN()

>Number.isNaN("foo")
false
>iNaN("foo")//グローバルなisNaN関数は使用禁止
数値以外を渡すと暗黙の型変換で判定がおかしくなる
true
ワッキーワッキー

JSのプリミティブ型て全てのデータがオブジェクトなのか?

結論 オブジェクトではない

プリミティブ型に用意されている各リテラル

Boolean

true false の二つの真偽値リテラル

Number

36や-9のように記述する数値リテラル。先頭に0xを付けることで16進数
0oを付けると8進数0bで2進数例: const foo = 0b101010
3.14や2.1e8などの形式で表現する浮動小数リテラルがある
1_000_000のように途中で投入された非連続のアンダースコアは無視される

Bigint

100nのように後ろにnを付けて表現する長整数リテラル

String

シングルクォート''またはダブルクォート""で囲まれた文字列リテラル
バッククォート``で囲むと、改行を含む複数テキストや
${}による式の展開が可能なテンプレートリテラル

Null

Nullリテラルであるnullはプリミティブ値nullを返す

Undefined

本書説明では:プリミティブ値undefinedが格納されているundefinedという名前のグローバル変数
gptはリテラルと言っている

ラッパーオブジェクトの暗黙生成

プリミティブ型のリテラルでも、プロパティやメソッドにアクセスした瞬間に
一時的なラッパーオブジェクトが作られます。
つまりオブジェクトではないが、プロパティにアクセスすると一時的にプリミティブ値がラッパーオブジェクトで内包されるのでプロパティが扱えるようになる?

let str = "hello";
console.log(str.toUpperCase()); // "HELLO"

// 実際は一時的に new String("hello") が裏で作られて使われる

この一時オブジェクトは使い終わると即破棄されます。

ワッキーワッキー

オブジェクト型とそのリテラル

オブジェクト型にもリテラルを持つものがある

配列リテラル

[1,2,3]の形式で記述する。[]はからの配列、arr[]という構文でn+1番目の要素にアクセスできる
Arrayオブジェクトのインスタンスとして生成される

オブジェクトリテラル

{key:value}の形式で記述する、キーには文字列またはシンボルが用いられる
(数値を指定すると自動的に文字列に変換される)
任意のプロパティ値にアクセスするにはobj[key]またはobj.keyの二つの構文が利用できる
Objectオブジェクトのインスタンスとして生成される

正規表現リテラル

/pattern/flagsの形式で記述する、正規表現のパターンで特殊文字の使い方は、
ほかの言語とほぼ共通、RegExpオブジェクトのインスタンスとして生成される
なお全角文字が含まれる場合はUnicodeを指定するuフラグの仕様を推奨
文字列のパターンをチェック・検索・置換したいときに使う

str[n] 個別の文字にアクセス

例: const str = "abc" str[1] //b

arr.at[-1] 末尾の要素にアクセスできるatメソッド

例: const arr = [1,2,3] arr.at[-1]//3

ArrayとObjectのコンストラクタはほぼ意味ない

RegExp以外出番なし

コンストラクタについて 要はインスタンスでオブジェクト作る機能

class Car {
  constructor(color) {//追加するもの初期状態を決める
    this.color = color;  // インスタンスのプロパティとして color を保存
  }
  drive() {
    console.log(`${this.color}の車が走ります`);
  }
}

let myCar = new Car("赤");

myCar (インスタンス)
├─ color: "赤"       ← constructorで渡されたデータ
└─ drive()           ← クラスで定義されたメソッド
プロパティとして考える
const myCar = {
  color: "赤",
  drive: function() {
    console.log(`${this.color}の車が走ります`);
  }
};

プロパティ = データを保持する

メソッド = データを使って何かする関数

コンストラクタで渡した値は プロパティとして保持される

ワッキーワッキー

関数宣言と関数式

変数に代入できるのが代入できないのが
const num = 1 + 1 //式
const foo = if(num === 2){retrun "2です"}//文
三項演算子は式だから変数に代入できる

関数宣言文は巻き上げと再宣言可能という問題もある

関数宣言文による定義
function double(n) {
    return n * 2;
  }
  関数式による定義
  const dbl = function double(n) {
    return n * 2;
  };

JSでは関数は組み込みオブジェクト Functionのインスタンスである

そして第一級オブジェクトである
他のオブジェクトと同様に変数に代入したり、配列の要素やオブジェクトのプロパティ値にしたり
他の引数として渡したり、別の関数の戻り値として設定できたりするということ
Rubyやjavaにはない性質
関数を第一級オブジェクトとして扱うことができるプログラミング言語の性質を第一級関数ともいう

まとめると 👇

JSの関数はオブジェクト(Function クラスのインスタンス)
→ だから普通のオブジェクトと同じように扱える。

第一級オブジェクト(ファーストクラスオブジェクト)

変数に代入できる

配列やオブジェクトの中に入れられる

引数として関数に渡せる

関数の戻り値として返せる

この「関数が第一級オブジェクトである」という言語の性質を 第一級関数(first-class functions) と呼ぶ。

👉 だから JavaScript では「関数をデータのように扱える」=「関数を自由自在に持ち運んで使える」言語だよ、ってこと。

関数は任意のオブジェクトのプロパティ値になることができる、そしてjsにおいては
単にオブジェクトのプロパティとなっている関数のことをメソッドと呼ぶ

const foo ={
bar:"bar"
baz:function (){//メソッドと呼ぶ
console.log("i am `baz` method");
}
}
foo.baz(); //I am `baz` method

メソッドのfunctionを省略することができる

const foo = {
  bar: "bar",
+  baz() {
+    console.log("i am `baz` method");
+  }
}
foo.baz(); //I am `baz` method

function を省略して書けるのはなぜ?

「実際には省略記法で、自動的に function が付く」

ワッキーワッキー

アロー関数式と無名関数

名前のない無名関数の二つの書き方
特徴としては名前が無いからメモリに残らず、変数に代入されなければ端から存在が消える

 <button
        onClick={() => {
          console.log("無名アロー関数");
        }}
      ></button>

      <button
        onClick={function () {
          console.log("無名関数");
        }}
      ></button>

メモリに残す方法

無名関数を変数に代入するだけ

const fc = function () {
    console.log("無名関数");
  };
  const arrow = () => {
    console.log("無名アロー関数");
  };

エラー発生時のスタックトレースに乗らないという噂は今は間違い

ほとんどのv8では変数名から推測してエラー発生時に関数名として表示してくれる
変数に無名関数を渡しても特に問題なし

スタックトレースとは

エラー発生時に、どの関数がどの位置で起きたのかをスタック(呼び出し履歴)形式で並べた記録

アロー関数の方がパフォーマンスが悪いのも今は間違い

gpt
過去(2015年くらい)の V8 では最適化が追いついておらず、
「アロー関数は遅い」というベンチマーク結果が出ることがありました。

しかし、現在(2025年時点)の V8 やモダンブラウザでは最適化が進み、
アロー関数と通常の関数にパフォーマンス差はほぼありません。

関数使い分け

「名前付きで呼び出す重要関数」 → 宣言文 function foo(){}

「変数に代入して渡す関数」 → 関数式 const foo = function(){}

「短く書けるコールバックや小さな処理」 → アロー関数 const foo = ()=>{}

ワッキーワッキー

さまざまな引数の表現

関数の引数を記述する際に用いるテクニック2つ

デフォルト引数 (m = 2)

デフォルト値を入れれば引数を省略しても値が設定される

const raise = (n, m = 2) => n ** m;// **はべき乗
  console.log(raise(2, 3)); //8
  console.log(raise(3)); //9

レストパラメータ ...

最後の引数の名前に...のプレフィックスを付けることで残りの引数を配列として受け取ることができる
文字の先頭に付けるのがプレフィックス、文字の後ろに付けるのがサフィックス
...rest

  const showNames = (a, b, ...rest) => {
    console.log(a);//"a"
    console.log(b);//"b"
    console.log(rest);//["c", "d", "e"]
  };
  showNames("a", "b", "c", "d", "e")

レストパラメータは可変長引数も実現している
可変長引数とは呼び出し時に引数の数がいくつであっても実行可能なもの

レストパラメータの第一引数で引数の全てを受け取る

  function showAllArgs(...args) {
    console.log(args);//[1, 2, 3, 4, 5]
  }
  showAllArgs(1, 2, 3, 4, 5);

レストパラメータを任意の名前を付けて取得するwith 分割代入

引数に分割代入を指定しているので配列から値を1つづつ取り出せる
呼び出しの引数は配列ではなくても、レストパラメータで受け取るので配列になる
指定した数だけ取得するので溢れた分はなくなる

const sum = (i, ...[j, k, l]) => i + j + k + l;
  console.log(sum(1, 2, 3, 4)); //10
  console.log(sum(1, 2, 3, 4, 5));

引数で分割代入

function printUser([id, name, ...other]) {
  console.log(id);    // "u123"
  console.log(name);  // "Alice"
  console.log(other); // ["Tokyo", "Engineer"]
}

printUser(["u123", "Alice", "Tokyo", "Engineer"]);

引数なしで普通に分割代入

引数の分割代入も同じ理屈で渡されているはず

const [id, name, ...other] = ["u123", "Alice", "Tokyo", "Engineer"];
console.log(id);    // "u123"
console.log(name);  // "Alice"
console.log(other); // ["Tokyo", "Engineer"]
ワッキーワッキー

クラスを表現する

クラスのようでクラスでない、jsのクラス構文

#className プライベートなプロパティ、同じクラスインスタンス内からthis.#classNameという記述でアクセスできる
constructorという名前で定義されているメソッドがコンストラクタ
static インスタンスしないで直接呼び出す クラスUserでstaticがfooならUser.foo
親クラスから子クラスを継承させるのは後から要素を追加したいときにすべてのの子に対して親を変更するだけで変更できるため

  // 親クラス
class Animal {
  name: string;

  constructor(name: string) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} が鳴いている`);
  }
}

// 子クラス(Animal を継承)
class Dog extends Animal {
  constructor(name: string) {
    super(name); // 親クラスの constructor を呼ぶ
  }

  // メソッドをオーバーライド(上書き)
  speak() {
    console.log(`${this.name} がワンワン吠えている`);
  }
}

const dog = new Dog("ポチ");
dog.speak(); // ポチ がワンワン吠えている
ワッキーワッキー

プロトタイプベースのオブジェクト指向

クラスベースとの違い

オブジェクト指向の2つの考え方

1. クラスベース OOP(例:Java, C#)

  • クラス(設計図) をまず作る
  • クラスをもとにして インスタンス(実体のオブジェクト) を作る
  • 継承は「クラスがクラスを継承」する形で行う

📌 イメージ

  • 「家を建てるために 設計図(クラス) がある」
  • 設計図から建てられた家が オブジェクト
  • 設計図をコピー・拡張して別の設計図を作れる(=継承)

2. JavaScript のプロトタイプベース OOP

  • JavaScript では本来「クラス」という設計図は存在せず、オブジェクトそのものから新しいオブジェクトを作る 仕組み
  • (ES6 で class 構文が追加されたが、見た目をクラスっぽくしただけ で、裏ではプロトタイプで動いている)
  • あるオブジェクトを元にして、新しいオブジェクトを作れる
  • 元のオブジェクトを プロトタイプ と呼ぶ
  • 新しいオブジェクトは、プロトタイプを通じて元のオブジェクトのプロパティやメソッドを使える

📌 イメージ

  • 「家(オブジェクトA)がすでにある」
  • それを元に「そっくりの家(オブジェクトB)」を作る
  • BはAを参考にして動く(Aが親、Bが子)
  • Aが変更されるとBにも影響がある(共有している)

✅ まとめ

  • クラスベース:設計図(クラス)をコピーして使う仕組み
  • プロトタイプベース:既存のオブジェクトを直接参照して使う仕組み

プロトタイプは「影分身」系キャラ

例:ナルトの影分身
本体(プロトタイプ)を変えれば、分身(子オブジェクト)も影響を受ける
本体が消えると分身も消えるイメージ

JSではあらゆるオブジェクトが何らかののプロトタイプを継承している

プロトタイプチェーンは最終的に空オブジェクトを経てnullに到達する

プロトタイプチェーンとは

オブジェクトが持たないプロパティやメソッドを、親(プロトタイプ)順番に探しに行く仕組み
参照元を 上へ上へたどって探索 する
最終的には 空オブジェクト {} → null に到達する

まとめ

「プロトタイプに参照元を探しに行って見つからない場合 → 空オブジェクト」
「そもそもプロトタイプがない → null」

ワッキーワッキー

分割代入とスプレット構文

JSは他の言語と比べて配列やオブジェクトの表現力がかなり高い

ショートハンド記法

const foo = "dummy";
const bar = "key";
const baz = "1024";

const obj1 = {
  foo: 4,            // キーは "foo"
  "fo": 8,           // キーは "fo"
  "<fuu>": 16,       // キーは "<fuu>"
  [bar]: 128,        // キーは "key"
  [`_${bar}2`]: 256, // キーは "_key2"
  baz: baz / 2,      // キーは "baz", 値は 512
};

console.log(obj1[bar]); // 128

オブジェクトのキーに変数や式を使う場合

ルール

1. キーをそのまま書くと文字列として扱われる

const foo = "dummy";
const obj = { foo: 4 }; 
// キーは "foo" という文字列で、変数 foo の値 "dummy" ではない
2. 変数や式をキーとして展開したい場合は [ ] で囲む

const bar = "key";
const obj = { [bar]: 128 }; 
// キーは bar の値 "key" になる
3. 式も同様に展開可能

編集する
const obj = { [`_${bar}2`]: 256 }; 
// キーは "_key2" になる
4. 値には普通に式を使える

const baz = "1024";
const obj = { baz: baz / 2 }; 
// キーは "baz", 値は 512
ワッキーワッキー

分割代入

const [a, b] = ["foo", "bar"];
  console.log(a, b); // foo bar
  const [, n] = [1, 4];
  console.log(n); // 4

  const [, , i, , j, , , k] = [1, 2, 3, 4, 5, 6, 7];
  console.log(i, j, k); //3 5 undefined

  const profile = { name: "kanae", age: 24, gender: "f" };
  const { name, age, gender } = profile;
  console.log(name, age); // kanae 24

応用編

分割代入時のdataという変数名をリネームしつつデフォルト値を空配列にする

const responses = {
    data: [
      {
        id: 1,
        name: "John",
        email: "john@example.com",
      },
      {
        id: 2,
        name: "Jane",
        email: "jane@example.com",
      },
      {
        id: 3,
        name: "Doe",
        email: "doe@example.com",
      },
    ],
  };

  const { data: users = [] } = responses; //dataをusersにリネームしつつつ、デフォルト値を空配列に設定
  console.log(users);
  // [
  //  { id: 1, name: "John", email: "john@example.com" },
  //  { id: 2, name: "Jane", email: "jane@example.com" },
  //  { id: 3, name: "Doe", email: "doe@example.com" }
  // ]

分割代入で data: users と書くと、オブジェクトのキー data の値を取り出して、変数 users に入れることができる

ワッキーワッキー

コレクションの中身を展開する構文

コレクション = 値の集合を管理するための構造
配列やオブジェクトは最もよく使うコレクション
Map や Set は キーや重複制御など特殊な要件に対応するためのコレクション

スプレット構文 配列やオブジェクトの名前の前に...をつけて展開する

//配列は展開した場所に順番が依存する
 const arr1 = ["a", "b", "c"];
  const arr2 = [...arr1, "d", "e"];
  console.log(arr2); //["a", "b", "c", "d", "e"]
//オブジェクトは展開した後に同じキーのプロパティがあると値が上書きされる
  const obj1 = { a: 1, b: 2, c: 3, d: 4 };
  const obj2 = { ...obj1, d: 99, e: 5 };
  console.log(obj2);// { a: 1, b: 2, c: 3, d: 99, e: 5 }

スプレット構文と分割代入組み合わせる

idだけ取り出して、それ以外をまとめる

 const user = { id: 1, name: "John", email: "john@example.com" };
  const { id, ...userWithoutId } = user;
  console.log(id, userWithoutId);// 1 { name: "John", email: "john@example.com" }
ワッキーワッキー

オブジェクトのマージとコピー

オブジェクト型の値は別の変数に代入しただけだと、参照渡しになって実体は共有されたまま

  // オブジェクト型は「参照渡し」
let obj1 = { name: "Alice" };
let obj2 = obj1;  // 参照をコピー(同じ実体を見ている)
obj2.name = "Bob";
console.log(obj1.name); // "Bob"
console.log(obj2.name); // "Bob"

プリミティブ値は値渡しなので元の値に影響なし

// プリミティブ型は「値渡し」
let a = 10;
let b = a;   // 値10がコピーされる
b = 20;

console.log(a); // 10(影響なし)
console.log(b); // 20

シャローコピー

新しい変数にスプレット構文で新しくコピーしたオブジェクトを代入するが
ネストされた、オブジェクトの中のオブジェクトは元を参照してしまう
シャローコピーの例(spread)

const obj = { name: "waki", hobby: { type: "game" } };
const shallow = { ...obj };

shallow.name = "tanaka";
console.log(obj.name); // "waki" ← 一階層目はコピーされたので独立

shallow.hobby.type = "music";
console.log(obj.hobby.type); // "music" ← ネスト部分は参照共有されている

シャローコピーは、トップレベルだけ新しいオブジェクトを作り、ネストされたオブジェクトは元の参照を共有するコピー

ディープコピーをして参照元に影響を及ぼさない方法structuredClone()

structuredClone()は渡されたオブジェクトをディープコピーする
多段階にネストされたオブジェクトや配列もコピー可能でかつ参照に影響を与えない

 const patty = { name: "Patty", email: "patty@example.com", age: 30 };

  const rolley = structuredClone(patty);
  rolley.name = "Rolley";
  rolley.email = "rolley@example.com";

  console.log(patty); // { name: "Patty", email: "patty@example.com", age: 30 }
  console.log(rolley); // { name: "Rolley", email: "rolley@example.com", age: 30 }
ワッキーワッキー

式と演算子で短く書く

ショートサーキット評価

またの名を短略評価という
&& || ! といった論理演算子を使い右辺の評価を左辺の評価に委ねる記法

Optional Chaining

オブジェクトに対して使える
一行でif文のように分岐できる
値が存在しない場合実行途中でundefinedを返す
値があればそのまま返す
メソッドに対しても書ける foo?.bar()?.baz()

const user = {
  address: { city: "Tokyo" }
};
const city = user.address?.city;//値が存在しない場合undefinedを返す
console.log(city); // "Tokyo"

?を付けない場合はエラーとなりそれ以降のコードは実行されない

  const user = {
    address: { city: "Tokyo" },
  };
  if (user.address) {
    const city2 = user.address2.city;e//?が無いのでエラーになり以降のコードは実行されない
    const city = user.address?.city;
    console.log(city); // "Tokyo"
    console.log(city2); // "Osaka"
  }

Nullish Coalescing ??

Null合体演算子
左辺がnullまたはundefinedの時に右辺が評価される
0や””空文字のようなfalsyな値はそのまま評価される
|| と違って 0, "" などは無視されない

Optional Chaining と組み合わせる例

const user = { name: "Alice" };
const city = user.address?.city ?? "Unknown";//左辺がなければ右辺を表示

console.log(city); // "Unknown" ← address が undefined なので右辺
ワッキーワッキー

モジュールを読み込む

JSモジュール三国志

他のソースコードファイルを読み込む仕組みモジュールシステムがなかった
なので長い間<script src="">タブでべたに読み込むしかなかった
読み込んだものすべてはグローバル空間へ展開されていた

この状況を大きく変えたのがNode.jsの登場だった

サーバーサイド言語としてのJSの体裁をを整えるためにCommonJSという標準API仕様を定める
プロジェクトを立ち上げた

CommonJS形式で自分でモジュールを書く場合

const moon ={
modifier: "prism"
transform(){
conosle.log(`moon ${this.modifier} power, make up!`);
}
module.exports = moon
}
exports,transform = function(){
console.log("Venus power, make up!")
}
const finish = function (){
console.log("Crescent beam!")
}
exports.finish = finish;

CommonJsだとimportではなくreauireを使う
.cjsという拡張子

CommonJSが提供したモジュールシステムでJSでもライブラリ作者が自分の開発したパッケージを他の開発者に手軽に使ってもらえる形で提供できるようになった

パッケージ管理システムのnpmとその公式リポジトリが構築され
たくさんのパッケージが公開され始めた

ブラウザのJSでもnpmパッケージ機能使いたい

Browserifyというプロダクト
npmパッケージをブラウザで使えるようにし、さらにモジュール間の依存解決をして、バンドル機能機能も備えた
モジュールをバンドルするからモジュールバンドラー
JS開発でも依存関係を気にせずパッケージを読み込んで使えるしアプリ自体もモジュールに分割できる
CommonJSは一時期天下統一した

モジュールの読み込みが同期的なのでメモリネットワークのオーバーヘッドが大きいフロントエンドでは順番にロードする待ち時間が長くなる
requireもexportsも条件分岐だろうとループ中だろうとどこでも呼べてしまう
静的解析が困難で出力ファイルの最適化が難しい

どこでも呼べるのが問題

CommonJS の require() は 関数なのでどこからでも呼べる
条件分岐の中
ループの中
関数やコールバックの中
つまり、実行時までどのモジュールを読み込むか決まらない

2. 条件分岐での問題

let mod;
if (process.env.NODE_ENV === "production") {
  mod = require("./prodModule");
} else {
  mod = require("./devModule");
}

ES Modulesが誕生し解決

静的解析を念頭に設計され、同期および非同期のローディング双方向をサポート
構文のimport/export記法も直感的、ECMAScript標準
ESMはブラウザから実行できる
<script>タグのtype="modules"という属性指定
タグの中のコードをモジュールとして定義するためのもの
これがないとコードが全てグローバルスコープに展開される従来のスクリプト形式で定義される
なのでimport文が使えない

ワッキーワッキー

ES Modules でインポート/エクスポート

膨大なライブラリやプロジェクトがCmmonJSで作られているので、Node.jsのプロジェクトでは
デフォルトでモジュールをCommonJSと扱ってしまう
ESMとして読み込ませるのはpackage.jsonに以下の設定が必要

"type":"module",

名前付きエクスポート 外部から使えるようにする

const ONE = 1;
const TWP = 2;
export { ONE, TWO };//定義したものをまとめてエクスポート
export const TEN = 10;//定義と同時にエクスポート

名前なし デフォルトエクスポート 読み込む側で名前を任意に付けられる

デフォルトエクスポートできるのは1モジュール(1ファイル)に付き1つだけ

const times = (n, m = 2) => n + m;
export default times;

as キーワードで名前変更できる

モジュール間で名前がバッティングしてまうときや、長い名前を省略して取り回したいときに
asキーワードを使うことで名前変更できる

`import {ONE,TWO as FOO} from".constants.js"//TWOからFOOに変更`

エクスポートの時にも使える
export {foo as bar}のように

デフォルトエクスポートと名前付きエクスポートが混在しているインポート

同じimport行でインポートする

import times,{plus} from"./modules/math.js";

プロジェクトのルートからインポートするならパスはいらない

import Button from "./components/Button";  // 相対パス
import React from "react";                  // node_modules から解決

モージュール集約

1つのファイルにまとめてエクスポートする
importが一行ですむ
補完機能が優秀なのでいらないかも

ワッキーワッキー

関数型プログラミング言語

関数型プログラミングでいう所の関数は数学的のy=f(x)
数学の関数は与えた引数が同じなら返る値も同じ
ランダムに返すものは違う

参照透過性

同じ入力に対して同じ出力を返すことが保証されていることを参照透過性という

関数型プログラミングとは

参照透過性的な関数を組み合わせて問題に対処していく宣言型プログラミングのスタイル

宣言型とは

プログラミングのパラダイムは命令型プログラミングと宣言型プログラミングの二つに大別される

命令型とは

最終的な結果を得るために状態を変化させる連続した命令文によって記述されるプログラミングスタイル
順序に従って行けばお目当ての料理ができるレシピに似ている

命令形に分類される、手続き型プログラミングとは

オブジェクト指向と手続き型を兼ね備えた言語がjava、C++、C#、Python、ruby
オブジェクト指向は命令型でも宣言型でもない
手続きというのは一連の命令文の組み合わせをまとめた、サブルーチンや関数と呼ばれるもの
主にグローバル変数としてまとめたデータを、複数の手続きを経て最終結果を得るプログラミングスタイル
gpt
手続き型プログラミングは「データ(グローバル変数)」+「処理(関数・サブルーチン)」で進める流れ重視のスタイル です。

宣言型

宣言型プログラミングでは出力を得る方法ではなく、出力の性質、あるべき状態を文字通り宣言することでプログラミングを構成する
SQLが最も有名な宣言型プログラミング言語
どんなデータを欲しいかを宣言することで出力が得られる

//命令型プログラミング
const octuples = [];
  for (let i = 1; i <= 100; i++) {
    if (i % 8 === 0) {
      octuples.push(i);
    }
  }
  console.log(octuples); // [8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96]

 { //関数型プログラミング
  const range = (start, end) =>
    [...Array(end - start + 1).keys()].map((i) => i + start);
  const octuples = range(1, 100).filter((i) => i % 8 === 0);
  console.log(octuples); // [8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96]

コード解説

[...Array(end - start + 1).keys()].map((i) => i + start);
Arrayは入力された値の長さを持つ空きスロットを作る(空文字列ではない)
スプレット構文を使うと未定義undefined空きスロットに入る
keys()でundefinedの代わりにインデックス番号をいれる[0,1,2]
mapでインデックスをループごとに受け取り、startの数を足す i + 1
これで配列に1から100までの値が入る

filterでコールバック関数を受け取り関数はmapのようにアイテムを受け取る、結果がtrueのものだけ新しい配列に渡す

二つのスタイルを対比させると異なる特徴がある

1つの目が

可変性不変性
手続き型可変性で12回の再代入と破壊的変更がある
ミュータブル可変性のプログラムが多いほど予測が困難になる
不変性守ることでプログラムから副作用が排除される、副作用とは外部の状態に影響を与えること
不変性のイミュータブル変数しか使ってなければそもそも問題が起きない
これが関数型プログラミングの優れたところ

イミュータビリティ

不変性を守ることでプログラムから副作用が排除される
副作用とはあるリソースの変更がアウトプットに影響を与えてしまう 関数においては参照透過性を壊してしまうこと、可変性なミュータブルのリソースがプログラムの中に 存在してると、それだけで副作用を生みやすくなる さらに関数型プログラミングでは、代入自体もできるだけ控える傾向にある 最初から代入しなければ壊れるものもない メソッドチェーンや演算子で式を繋いでいき、関数もその場限りのものなら無名関数`のまま使う

副作用例

「副作用 (side effect)」という言葉の説明に出てくる リソースの変更がアウトプットに影響を与える という部分をかみ砕くと、こういうことです。

1. 前提:関数の「アウトプット」

関数の出力(アウトプット)は、本来 入力(インプット)だけで決まるのが理想です。
例えば:

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

入力が 2, 3 なら、出力は必ず 5。

外部の状況(時間、変数の状態など)には左右されません。
👉 これを 純粋関数 (pure function) といいます。

2. リソースの変更が影響する場合

一方で、関数の外にあるリソース(状態、変数、ファイル、DB、ネットワークなど)を変更したり参照したりすると、出力が外部環境によって変わってしまいます。

例:

let counter = 0;

function addAndCount(a: number, b: number): number {
  counter++; // 外部リソース(グローバル変数)を変更
  return a + b + counter;
}

最初に呼ぶと a+b+1
次に呼ぶと a+b+2
さらに呼ぶと a+b+3

👉 同じ入力でも、外部リソースの状態が変わるので出力が変わる。
これが「リソースの変更がアウトプットに影響を与える」という意味です。

3. まとめると

純粋関数:入力だけで出力が決まる。リソース変更なし。副作用なし。
副作用のある関数:入力以外の要素(外部リソース)によって出力が変わる。

対照的な特徴の二つ目として

手続き型が文を多用する一方で
関数型では式を組み合わせてプログラミングを構成する
手続き型の方はfor文によるループの中でif文によって条件分岐をしてといううように
値を返さないサブルーチンの中で計算が実行されている
「サブルーチン」は 「値を返さない処理の部品(for文+if文を使ったような命令的処理)」
関数型の方はすべてが値を返す式の組み合わせでそれらが左辺から右辺へ評価されていき
最終的な値へ到達する形になっている
そのおかげでコードがシンプルになる

対象的な特徴三つ目

手続き型ではボトムアップ的に積み上げていって最終成果物を完成させる
最初octuplesは空配列だったのが、要素をひとつずつ追加されていって完成に至る
一方関数型では、最初から関数型を見据えた上で大きな大雑把なところから絞りこんでいく
形になる
このサンプルではまず1から100の整数配列を作った上で、そこから8で割り切れるものだけを抜き出している

二つの違いを例で説明

手続き型(ボトムアップ的)

「最初から1個ずつ順番に処理していく」やり方です。
例:
1から始める

8で割り切れるか調べる

割り切れたら結果に入れる

2に戻って100まで続ける
👉 つまり、細かい処理を積み重ねて最終的な答えを作っていくイメージ。

関数型(トップダウン的)

「まず全体の流れ(大きな処理の枠組み)を決めてから、中で絞り込む」やり方です。

例:
まず「1から100までの配列を作る」という大きな集合を用意する

そこから「8で割り切れるものだけを残す(filterする)」と決める

👉 大きな枠を用意してから、それを加工していくイメージ。

サンプルコード(関数型)

const result = Array.from({ length: 100 }, (_, i) => i + 1) // 1から100
  .filter(n => n % 8 === 0); // 8で割り切れるものだけ

この場合

「1から100の配列を作る」 → 大きな集合をまず用意
「filterで8の倍数だけ残す」 → 後から条件を絞る

🔑 まとめると:

手続き型は「1個ずつ処理しながら積み上げていく」
関数型は「まず大きく集合を作って、そこから加工・変換していく」

手続き型プログラミングが終わりの見えず絶えず似たような画面がひたすら
繰り返されるスペースインベーダー
関数型プログラミングは開始早々マップ対岸にラスボスであるりゅうおうの城が見えてる
ドラクエ1みたいイメージ

熟練のエンジニアの書く関数型のコードは意図がはっきりしてて可読性が高い
条件分岐やループであっちこっち行かない来たりしない

現代の言語はマルチパラダイム

複数のパラダイムの性質を併せ持っているものも多いから、明確に分類するのが難しい
JSは明確に関数型に分類はできないけど、第一級関数をサポートしているから
関数型プログラミングをやろうと思えばできる

ワッキーワッキー

コレクションの反復処理

配列の反復処理

コレクションの反復処理 = まとめられたデータを1つずつ処理すること。
配列の反復処理 = for文・for...of・forEach・map・filter など色々な方法がある。
関数型っぽい方法(mapやfilter)は「データを変換したり絞り込んだり」するときに直感的で便利。

Arrayオブジェクトのプロトタイプメソッド

map対象の配列からデータを1つずつ取り出して処理をして新しい配列を返す
filter対象の配列からデータを1つずつ取り出して条件に合致するものだけを新しい配列として返す
find対象の配列からデータを1つずつ取り出して条件に合致する``最初のデータを返し、ない場合はundefinedを返す
findIndex対象の配列から1つずつ取り出して条件に合致するデータのインデックス番号を返す
every配列から1つずつ取り出してすべての条件に合致するか真偽値で返す
some配列から1つずつ取り出して1つでも条件に合致するか真偽値で返す

##メソッドの引数として渡す関数reduce()とsort()
reduce配列から1つずつ取り出して第二引数(currカレント)にいれる、第一引数には返り値が入る(acc 累積)これを繰り返す(第三引数にはindex、第四引数はarrayが入る)
reduceだけで配列の平均値を出す方法

  [1, 2, 3, 4].reduce((acc = 0, curr, index, array) => {
    acc += curr;
    if (index === array.length - 1) {
      console.log(acc / array.length);
      return acc / array.length;
    }
    return acc;
  });

sort配列から1つずつ取り出して第一引数に0番からいれる、第二引数に次のデータをいれる
データの数値が低い場合はインデックスを-1多い場合はインデックスを+1する
比較して順番入れ替えがなければ1番を第一引数にいれるて第二引数に次のデータをいれて比較
最終的な順序の配列を代入する(元の変数を破壊する)

仕組みの予想:
インデックスを-1したり+1している [3,2,1]の時 3と2を比較順番入れ替え [2,3,1] 2と3を比較順番変わらない 2と1を比較順番入れ替え [2,1,3] 2と1を比較順番入れ替え [1,2,3] すべて比較し動かないので終了

覚えるべき破壊的メソッド

sortとreverse
並べ替え可能だが元の配列が変わってしまう

ES2023ではtoSortedとtoReversedが代わりになる

これらを代わりに使うことで非破壊的に並べ替え可能

nums.toSorted((a, b) => (a > b ? 1 : -1))
nums.toReversed()

ES2023が有効ではない環境の場合 スプレット構文を使う[...nums]

スプレット構文を使って展開して新しい配列を作ってからreverseすると元の配列は破壊されない

  const nums = [1, 3, 4, 2, 6, 5];
  console.log(
    [...nums].reverse(),
    nums
  );

配列の反復処理の値を返さない構文 forEach for...of

あえて使うならforEachだが、どちらも使わない
これらは値を返さないのでミュータブルな変数を書き換えるといった副作用を起こす可能性がある
ほとんどの処理はmapやfindで代替可能なのだ

//forEach
  const nums = [1, 3, 4, 2, 6, 5];
  nums.forEach((n) => {
    if (n % 2 === 0) {
      console.log(n);
    }
  });
//for of
  for (let n of nums) {
    if (n % 2 === 0) {
      console.log(n);
    }
  }

includes 指定した値の要素が1つでも含まれているかを真偽値で返す

値が存在するか確認するのがincludes
条件に合致するものがあるかを確認するのがsome

  const nums = [1, 3, 4, 2, 6, 5];
  console.log(nums.includes(5));//true
  console.log(nums.includes(8));//false

JSで任意の回数だけ繰り返し処理をしたいとき外部ライブラリなしで手っ取り早く書く方法

console.log([...Array(3)]);// [undefined, undefined, undefined]
  console.log(
    [...Array(3)].map((_, n) => {
      return n + 1;
    })
  ); // [1, 2, 3]
  
  console.log([Array(3)].keys()); // [0, 1, 2]
  console.log([...Array(3).keys()].map((n) => n + 1)); // [1, 2, 3]
ワッキーワッキー

オブジェクトの反復処理

狭義のオブジェクトつまり標準組み込みオブジェクトObjectを直接継承するオブジェクトの反復処理
巷ではfor...in文が使われたコードをよく見かけるけど。配列の時のfor...ofと同じ理由で進められない
Object自身がもつメソッドを使っていったん配列を形成する方法がAirbnbのスタイルガイドでは推奨される

  const obj = { a: 1, b: 2, c: 3 };
  const keys = Object.keys(obj); //["a", "b", "c"] keyのリストを返す
  const values = Object.values(obj); //[1, 2, 3] 値のリストを返す
  const entries = Object.entries(obj); //[["a", 1], ["b", 2], ["c", 3]]
  console.log(keys);
  console.log(values);
  console.log(entries);

entriesの便利な使い方
データベースから取得したオブジェクトをmapで処理する際に便利
プロパティ名と値のペアごとに配列に格納して、まとめて新しい配列にいれる
const obj = { a: 1, b: 2, c: 3 };
[["a", 1], ["b", 2], ["c", 3]]

  const obj = { a: 1, b: 2, c: 3 };
  Object.entries(obj).map((item) => {
    return console.log(item); //[["a", 1], ["b", 2], ["c", 3]]
  });

高階関数

引数に関数を取ったり、戻り値として関数を返したりする関数のこと
mapやfilterなど(無名関数を引数に受け取る)
コールバック関数はこの引数に関数を受け取るということ
コールバック関数は通常アロー関数を使う

ワッキーワッキー

高階関数を便利にするカリー化

カリー化とは複数の引数を単体で渡すように分離すること
分離することで関数の固定化(部分適用)ができるようになる
一つの引数を受け取り、関数を返す、ここで一つ目の引数は固定される
返り値の関数の引数の値は固定化されないので自由に指定できる
つまり、関数Aの値nを固定化しつつ、返り値関数Bの引数mは自由に指定できる

//通常の関数
  const Multiple = (n, m) => n * m;
  Multiple(3, 4); // 12

  //カリー化
  function withMultipleFc(n) {
    return (m) => n * m;
  }
  withMultipleFc(3)(4); // 12

  //アロー関数のカリー化
  const withMultiple = (n) => (m) => n * m;
  withMultiple(3)(4); // 12
ワッキーワッキー

閉じ込められたクロージャの秘密

関数を関数で包むこと、関数の中で作られた関数が外の変数を覚えている仕組みのこと
ダメな例 グローバル変数を使っている

let count = 0;//グローバル変数なのでどこからでもいじれる
  function increment() {
    return count++;
  }

関数を関数で包む

 function counter() {
    let count = 0;
    function increment() {
      return ++count;
    }
    return increment;
  }
  const increment = counter();
  console.log(increment()); // 1
  console.log(increment()); // 2

アロー関数でクロージャ

const counter =
    (count = 0) =>
    (adds = 1) =>
      (count += adds);
  console.log(counter(2)(5));// 7

閉じ込めている外側の関数をエンクロージャーという
外側の関数にあるローカル変数を内側の関数から見るとローカル変数ではなく、自由変数になる
なぜ自由変数が状態を保持するのか、それはJSのメモリ管理の仕組みを知る必要がある

メモリのライフサイクル メモリの存在している時間(寿命)

1必要なメモリを割り当てる
2割り当てられたメモリを使用する
3必要がなくなったら、割り当てたメモリを解放する
JSのような高水準言語は3を手動で行わなくても、自動で不要になった領域を判別して解放する
機能が装備されている、これをガベージコレクターという死神みたいなもの
この死神がいないとゾンビ映画のように死者が溢れ返って生者の居場所を圧迫してしまう
これがメモリリークと呼ばれる現象
確保された領域がこの世にいるべきではない死者かどうかを判断するのがガベージコレクターだ
カレントスコープにいるものは問答無用で生者

スコープを外れたものが生きているか死んでいるかを判定する条件

他の生者から必要とされているかどうかということ、つまり参照
スコープ外から参照できないとGCされる

// この変数と関数はガベージコレクションの対象になります
  // 理由:
  // 1. ブロックスコープ内で定義されている
  // 2. ブロック外からアクセスできない
  // 3. 他の場所から参照されていない
  // 4. 関数saveHimはheを参照しているが、saveHim自体も外部から参照不可能
  if (true) {
    const he = "kakeru";
    function saveHim() {
      console.log(he);
    }
    // ここでブロックが終了すると、heとsaveHimは到達不可能になりGCされる
  }
ワッキーワッキー

Enum型とリテラル型

セレクトボックスのように列挙したものの中から一つだけ取得したい場合
TSではどういう型が使えるのか
二つの方法がある

Enum型 デフォルトが数値で型安全が保証されないので非推奨

  enum Color {
    Red,
    Green,
    Blue,
  }
  let red = Color.Red;
  console.log(red); // 0
  red = Color.Green;
  console.log(red); // 1

文字列Enumを使えば一応型安全はできる

enum Color {
  Red = "RED",
  Green = "GREEN",
  Blue = "BLUE",
}
let red: Color = Color.Red;
// red = "YELLOW"; ❌ エラー
// red = "RED";  文字列REDはEnum型のColor.Redと型が一致しないのでエラー

リテラル型 literal 文字通りのという意味

特定の文字列以外を許さない型なので再代入できない

let Tom: "cat" = "cat";
  Tom = "mouse"; //エラー 文字列Literal型の変更はできません

演算子|バーティカルを使ってリテラル型を列挙型のように扱う

 let Animal: "cat" | "dog" | "mouse" = "cat";
  Animal = "dog";
  Animal = "mouse";
  Animal = "foo"; //エラー 文字列Literal型の変更はできません

Boolean型と数値型はリテラル型を使ってもあまり意味ない

文字列リテラル型 → 「特定の単語」だけを型にできる → ユースケースが多い
Boolean リテラル型 → true or false しかないのであまり使わない
数値リテラル型 → 型としては可能だが実用性が低い(enum や定数の方が便利)

constだと数値リテラル型になる

const number = 1;//数値リテラル型

タプル型 順番や数に制約を付けられる配列の型

型と順番と個数だけ決まっているものって、よく使われるのは引数の型

const charAttrs: [number, string, boolean] = [1, "patty", true];

引数に使うのでレストパラメータを使って複数を受け取ることができる

const charAttrs: [number, ...string[]] = [1, "patty", "cat"];

タプルは「引数を順番通り型付きで受け取りたいとき」に便利です。例えば、座標 [x, y] や RGB [r, g, b] など。

ワッキーワッキー

any unknown never

何者でもあったり、何者でもなかったりする型

any

anyで定義された変数はいかなる値も受け付ける

 let val: any = 100;
  val = "hello";

構造の不明なJSONファイルをパースしてそのままオブジェクトとして使う場合、事前に一律の型を当てはめるのが難しい事がある
そういう時にanyとするが使ったら負けと思った方がいい

anyはコンパイルで事前エラー検証できない

 const str1 = { id: 1, username: "patty" };
  const user1 = JSON.stringify(str1);
  console.log(user1.foo.hoge);//存在しないオブジェクトの呼び出しが可能になる
//実行するとエラーがでる

unknown型 anyの型安全版

任意の型の値を代入できるのはanyと同じだが、何のプロパティもプロトタイプメソッドも持たない
なのでメソッドを呼び出してもエラーになる

  const str1 = { id: 1, username: "patty" };
  const user1: unknown = JSON.stringify(str1);
  console.log(user1.foo.hoge);//存在しないのでエラー
  console.log(user1.id);//実際に存在する値でもunknownなので型エラーになる

このままだと存在する値も型エラーになる(コンパイル時に型エラーだけで実行できる)
値の型を特定してあげる処理を追加する必要がある
コンパイルも通るし、型安全も保証される

型ガード

never型 何も代入できない型

friendは三種類の引数を受け取れる
渡された引数がcaseに該当していなければdefaultが動く
defaultはnerverなので値が渡されると到達するはずがないのでエラーになる
これで開発者は入力忘れに気づける

 const greet = (friend: "Serval" | "Caracal" | "Cheetah") => {
    switch (friend) {
      case "Serval":
        return "Hello, Serval!";
      case "Caracal":
        return "Hello, Caracal!";
      // case "Cheetah":
      //   return "Hello, Cheetah!";
      default: {
        const check: never = friend;
   return check;
  //caseに該当する引数がなければdefaultに値が入るのでネバーはエラーになる
      }
    }
  };
  console.log(greet("Serval")); // Hello, Serval!
ワッキーワッキー

関数とクラスの型

関数の定義型

TSのコンパイラオプション(tsconfig.json)にnoImplicitAnyが指定されてないと
引数の型定義がなくても暗黙的にanyが当てられてコンパイルエラーがでない
strictをtrueにすることでnoImplicitAnyが一緒に設定される

strictで追加される主な設定

✅ strict: true で有効になる主なオプション
(バージョンによって微妙に違いますが、代表的なものを挙げます)
noImplicitAny
→ 暗黙の any を禁止

noImplicitThis
→ this が暗黙に any になるのを禁止

alwaysStrict
→ 出力される JS に "use strict"; を自動で付ける

strictNullChecks
→ null や undefined を他の型に勝手に代入できないようにする

strictFunctionTypes
→ 関数の引数の互換性チェックをより厳格にする

strictBindCallApply
→ bind / call / apply を使うときに型チェックする

strictPropertyInitialization
→ クラスのプロパティを コンストラクタで必ず初期化しないとエラー

useUnknownInCatchVariables(TS 4.4 以降)
→ catch (e) の e が暗黙に any ではなく unknown になる

コンポーネントの定義にはFunctionキーワードによる関数宣言を使う

アロー関数より完結にかける

ワッキーワッキー

関数宣言にジェネリクスを使う<T>

呼び出し時に任意に変えられる型引数をつけられる
<T>の文字は慣例
Tはtype
二つ目がアルファベット順でUの時もある
Keyの頭文字でK
ValueのVなど

function toArray<T>(arg1: T, arg2: T) {
    return [arg1, arg2];
  }
  toArray<string>("hello", "world"); //["hello", "world"]
  toArray<number>(1, 2); //[1, 2]
  toArray<boolean>(true, false); //[true, false]
//アロー関数の場合はカンマを付ける必要がある
  const toArrayArrow = <T,>(arg1: T, arg2: T) => [arg1, arg2];
  toArrayArrow<string>("hello", "world"); //["hello", "world"]

可変長引数 T[]

ただカッコ付けるだけ

  function toArray<T>(...arg1: T[]) {
    return [arg1]; //配列の中に配列が入るT[][]
  }
  console.log(toArray<string>("hello", "world"));

  //アロー関数
  const toArrayArrow = <T,>(...arg1: T[]) => [...arg1]; //配列を展開して返すT[]
  toArrayArrow<string>("hello", "world"); //["hello", "world"]
  console.log(toArrayArrow<string>("hello", "world"));

型引数は関数だけではなくインターフェースに定義したり、引数にデフォルト値を付けられる

型の引数をジェネリクスでインターフェースに付けることで
型の呼び出し時に型引数を任意に指定できる
デフォルト値をつけていれば何も指定しなければデフォルト値が型となる

interface User<V = number> {//型引数を付けでデフォルト値を設定
    name: string;
    age: V;
  }

  const user: User = {//デフォルト値
    name: "John",
    age: 30,
  };
  const user2: User<string> = {//文字列指定
    name: "John",
    age: "30",
  };
ワッキーワッキー

TypeScriptでのクラスの扱い

 class Rectangle {
    readonly name = "Rectangle";
    sideA: number;
    sideB: number;

    constructor(sideA: number, sideB: number) {
      this.sideA = sideA;
      this.sideB = sideB;
    }

    getArea(): number {
      return this.sideA * this.sideB;
    }
  }
ワッキーワッキー

型の名前と型合成

型エイリアスVSインターフェース

  //無名のリテラル型に参照するための別名を付ける
  type Unit = "USD" | "EUR" | "JPY";
  type TCurrent = {
    unit: Unit;
    value: number;
  };
  //インターフェースは型の宣言なのでオリジナルの名前が与えられる
  interface ICurrency {
    unit: Unit;
    value: number;
  }
  const current1: TCurrent = { unit: "USD", value: 100 };
  const current2: ICurrency = { unit: "EUR", value: 200 };

インターフェースで定義できるのはオブジェクトとクラスの型だけ
型エイリアスはリテラル型のような他の型も定義できる

インターフェースは拡張に対してオープンな性質

同じ名前でも再宣言にならない
新しい型のプロパティ宣言が増えていくだけ

interface User {
    name: string;
  }
  interface User {
    age: number;
  }
  const rolley: User = {
    name: "John Doe",
    age: 30,
  };

プリミティブな値ユニオン型やタプルの型定義をする場合は型エイリアスを利用し、オブジェクトの型を定義する場合はインターフェースを使うことを推奨しています。

インターフェースと型エイリアスの使い分けに悩む場面が多く開発スピードが落ちてしまうのであれば、型エイリアスに統一して書く方針にする考え方もあります。

ワッキーワッキー

ユニオン型とインターセクション型

TypeScriptでは既存の型を組み合わせて、複雑な型表現できる

ユニオン型 |

数値でも文字列でも許容可能number|string

  let id: number | string = 123;
  id = 456;
  id = "456";
  id = true; //型エラー

いづれかの型が適用される複合型になる

リテラル型

"foo"|"hoge" |"piyo"
本来リテラル型プリミティブ型特定の値そのものだけを許す型
ユニオン型組み合わせる列挙型のように使えるようになる
"foo"|"hoge" |"piyo"このような型を文字列リテラルユニオン型という

ユニオン型にオブジェクトを適用

同じキー命のプロパティがそれぞれAとBに合っても別々のどっちかの型になる
type AorB = { foo: number; bar?: string | undefined; } | { foo: string; }

interface A {
    foo: number;
    bar?: string;
  }
  interface B {
    foo: string;
  }
  interface C {
    bar: number;
  }
  type AorB = A | B;
  type AorC = A | C;

インターセクション型 交差型

複数の型を演算子&で並べて行く
ユニオン型がAまたはBと適用範囲を増やしていくのに対して
インターセクション型はAかつBと複数の型を一つに結合する
オブジェクト型の合成に使われる

プリミティブ型に使わない理由は数値かつ文字列なんて存在しないからnevar型になってしまう

type Some = number & string;
  let id: Some;//nevarとなる
interface T {
    foo: number;
  }
  interface U {
    bar: string;
  }
  interface V {
    foo?: number;
    baz: boolean;
  }
  type TnU = T & U; // { foo: number; bar: string; }//継承する
  type TnV = T & V; // { foo: number; baz: boolean; }//bazがあるならfooは必須、ないならfooは必須ではない
  type VnU = V & U; // { foo?: number; bar: string; baz: boolean; }//継承する
  type VnTorU = V & (T | U); //{foo:number;baz:boolean;}or{ foo?: number; bar: string; baz: boolean; }
  const foo: VnTorU = { foo: 123, bar: "hello", baz: true }; //ok
  const foo2: VnTorU = { bar: "hello", baz: true }; //ok
  const foo3: VnTorU = { foo: 123, baz: true }; //ok
  const foo4: VnTorU = { baz: true }; //fooがない場合はbarが必須なのでエラー

二つのパターンに分かれて行われる
fooが必須のパターンと必須ではないパターン

// パターンA
{
  foo: number;   // 必須
  baz: boolean;
}

// パターンB
{
  foo?: number;  // 任意
  baz: boolean;
  bar: string;
}

インターフェースでできることは型エイリアスにもできる

stringとnumberのような互換性のない継承の場合
インターフェースはエラーになる
型エイリアスはneverが入る

  type Unit = "USD" | "EUR" | "JPY" | "GBP";
  interface Currency {
    unit: Unit;
    amount: number;
  }
  interface IPayment extends Currency {//ここと
    date: Date;
  }
  type TPayment = Currency & { date: Date };//ここは同じ結果になる

  const date = new Date("2023-01-01");
  const payA: IPayment = { unit: "JPY", amount: 10000, date };
  const payB: TPayment = { unit: "USD", amount: 100, date };
ワッキーワッキー

型のNull安全性を保証する

文字列にnullやindefinedを代入できてしまう
型安全性に問題がある

 const foo: string = null;//nullはエラーになっていた
  const foo2: string = undefined;//初期設定ではエラーにならない

strictNullCheckを追加すると厳格にエラーを出せる
tsconfig.json

  "compilerOptions": {
    "strictNullChecks": true
  }

nullを許容した場合はユニオン型を使う

strictNullChecksを有効にすることでコンパイラは他の型にnullやundefinedを代入できなくなる
Null安全性が保証されるし、ユニオン型による明示的なNull許容構文は開発者にNullアクセス可能性
をちゃんと意識させてくれる

let foo: string | null = "foo";//明示的で分かりやすい
  foo = null;

要素が省略可能だとundefinedの可能性を教えてくれる

  interface Resident {
    familyName: string;
    lastName: string;
    mom?: Resident;
  }

  const geetMomName = (resident: Resident): string => {
    return resident.mom.lastName;//エラーundefinedの可能性がある
  };

Resident型のmomプロパティは省略可能なのでundefinedへのアクセス可能性があって
コンパイルするまでもなくvscodeが教えてくれる
これがNull安全性が保証されている恩恵
テストを書かなくても実行時のNullアクセスエラーを未然に防いでくれる

使ってはいけない解決方法 非Nullアサーション演算子 「!」

ここは絶対nullもundefinedも入りませんよとコンパイラの型チェックを強引にやらせるもの
Null安全性を壊すもので、実際に値がnullやundefinedだったらランタイムエラーになる
開発中の一時期な確認に使うためくらいならいいが、本番に残すのは辞めよう

ワッキーワッキー

型表現に使われる演算子

これまでもジェネリクスで型引数を使ったり、型を|や&で合成してたがそれの延長だ
型の表現に使える演算子はこれまでに|と&の二つを紹介したけどまだ他にある

typeof演算子 変数から型を抽出

通常では渡された値の型の名前を示す文字列を返すけど、型のコンテキストで用いると変数を抽出してくれる

console.log(typeof 100); // "number"
  const arr = [1, 2, 3];
  console.log(typeof arr); // "object"

  type NumArr = typeof arr; // number[]

  const val: NumArr = [4, 5, 6];
  const val2: NumArr = null; // 型が number[]なのでエラーになる

GPTの解説

typeof は JavaScript の変数や定数の型を TypeScript で取得する演算子です。
つまり、オブジェクトそのものの「型情報」を取り出すことができます。

  1. 例:オブジェクトの場合
const todo = { id: "1", title: "勉強する", completed: false };
type TodoType = typeof todo;
typeof todo は { id: string; title: string; completed: boolean } という型になる

これで変数 todo と同じ構造の型を他の場所で使える

型推論で既存の変数から型を抜き出せるこの機能は、コードのスリム化に役立つことが多い
実務で重宝する

in演算子

通常の式では指定した値がオブジェクトのキーとして存在するかどうかを真偽値で返したり
for...in文ではオブジェクトからインクリメンタルにキーを抽出するのに使われる
一方型コンテキストでは、列挙された型の中から各要素の型の値を抜き出してマップ型というのを作る

  const obj = { a: 1, b: 2, c: 3 };
  console.log("a" in obj); // true オブジェクトのキーが存在するか確認
  for (const key in obj) {// "a", "b", "c" オブジェクトのキーを順番に取得
    console.log(key); 
  }

  type Fig = "one" | "two" | "three";
  type FigMap = { [key in Fig]?: number };
  const figMap: FigMap = {
    one: 1,
    two: 2,
    three: 3,
  };

  figMap.four = 4; //コンパイラエラー

keyof 型のオブジェクトのキーをリテラルユニオン型取得する

type Todo = {
    id: string;
    title: string;
    completed: boolean;
  };
  type TodoKeys = keyof Todo;//"id" | "title" | "completed"
  const todoKey: TodoKeys = "id"; 

keyof typeof item 既存のオブジェクトのキーだけを型として使う

keyofは型オブジェクトから取得するが、keyof typeofは普通のオブジェクトから取得する
どちらもリテラルユニオン型

const todo = {
    id: "1",
    title: "Learn TypeScript",
    completed: false,
  };
  type TodoKeys = keyof typeof todo;// //"id"|"title" | "completed"
  const todoKeys: TodoKeys = "id";