let name: string = "梨花";
let age: number = 11;
let isAlive: boolean = true;
let members: string[] = ["圭一", "レナ"];
let user: { name: string; age: number } = {
name: "梨花",
age: 11
};
let id: number | string = 123;
type Status = "pending" | "done";
let data: any = "危険"; // 使わない
let secret: unknown = "安全";
function log(): void { }
function error(): never {
throw new Error();
}
// 型を書かなくてもOK
let price = 1000; // 自動的に number
let items = ["A", "B"]; // 自動的に string[]
function process(value: string | number) {
if (typeof value === "string") {
return value.toUpperCase(); // string として使える
}
return value.toFixed(2); // number として使える
}
オブジェクト型を扱う方法
型エイリアスは、型に別名をつけることができます
// オブジェクト型にエイリアスを付ける
type ClubMember = {
name: string;
age: number;
weapon?: string; // オプショナルプロパティ
curse: boolean;
};
let rena: ClubMember = {
name: "竜宮レナ",
age: 16,
weapon: "鉈",
curse: true
};
型エイリアスは、オブジェクト型だけでなく、あらゆる型に名前を付けることができます
// プリミティブ型
type CharacterName = string;
type Age = number;
// ユニオン型
type Weapon = "鉈" | "バット" | "注射器" | "包丁";
// 関数型
type CurseFunction = (target: string) => boolean;
// 配列型
type CharacterList = string[];
type SuspicionLevels = number[];
// タプル型
type Coordinate = [number, number];
型エイリアスは交差型「&」を使って拡張できます
// 基本の型
type Person = {
name: string;
age: number;
};
// 交差型で拡張
type ClubMember = Person & {
weapon?: string;
clubRole: string;
};
let mion: ClubMember = {
name: "園崎魅音", // Person より継承
age: 16, // Person より継承
weapon: "エアガン", // ClubMember 独自
clubRole: "部長" // ClubMember 独自
};
インターフェースはオブジェクトの構造を定義します
// 基本的なインターフェース
interface Character {
name: string;
age: number;
isAlive: boolean;
weapon?: string; // オプショナルプロパティ
}
let keiichi: Character = {
name: "前原圭一",
age: 16,
isAlive: true
};
インターフェースは extends
キーワードで他のインターフェースを拡張できます
interface Person {
name: string;
age: number;
}
interface ClubMember extends Person {
weapon?: string;
clubRole: string;
}
interface VillageElder extends Person {
position: string;
knowsSecret: boolean;
}
// 基本のインターフェース
let mion: ClubMember = {
name: "園崎魅音", // Person より継承
age: 16, // Person より継承
weapon: "エアガン", // ClubMember 独自
clubRole: "部長" // ClubMember 独自
};
// extendsで拡張
let oryou: VillageElder = {
name: "園崎お魎", // Person より継承
age: 78, // Person より継承
position: "当主", // VillageElder 独自
knowsSecret: true // VillageElder 独自
};
interface
: 宣言マージ可能interface ClubMember {
name: string;
age: number;
}
// 同じ名前で追加宣言可能
interface ClubMember {
clubRole: string;
}
// 自動的にマージされる
let mion: ClubMember = {
name: "園崎魅音",
age: 16,
clubRole: "部長"
};
type
: 宣言マージ不可type ClubMember = {
name: string;
age: number;
}
// エラー: 重複した識別子
// type ClubMember = {
// weapon?: string;
// clubRole: string;
// }
// 代わりに交差型を使用
type ExtendedClubMember = ClubMember & {
weapon?: string;
clubRole: string;
};
// A
type Status = "active" | "inactive" | "pending";
// B
type Callback = (data: string) => void;
// C
type UserArray = User[];
// D
type UserTuple = [string, number, boolean];
// A
type Status = "loading" | "success" | "error";
// B
type Handler = (event: Event) => void;
// C
type Config = {
timeout: number;
};
type Config = {
retries: number;
};
interface Stand {
name: string;
power: number;
}
interface Stand {
name: number; // 型が違う!
user: string;
}
name: string | number
になる比較項目 | type |
interface |
---|---|---|
宣言マージ | ✕ 不可(エラー) | ✓ 可能 |
拡張方法 | & (交差型) |
extends |
定義できる型 | プリミティブ・ユニオン・関数・条件型等 | オブジェクト構造のみ |
再定義 | ✕ 同名で定義不可 | ✓ OK(マージ) |
複雑な型の表現 | ✓ 条件型・ユニオン型等に強い | ✕ 不向き |
使用推奨 | 複雑・柔軟な型 | 標準的なオブジェクト |
// A - Stand能力のID
"the-world" | "star-platinum" | "crazy-diamond";
// B - Stand情報のオブジェクト
{
name: string;
user: string;
power: number;
}
type Stand = {
name: string;
power: number;
};
interface Hamon {
power: number;
}
type StandUser1 = Stand & { // A
user: string;
};
type StandUser2 = Stand extends { // B
user: string;
}
interface HamonUser1 & Hamon { // C
user: string;
}
interface HamonUser2 extends Hamon { // D
user: string;
}
StandUser1
StandUser2
HamonUser1
HamonUser2
extends
、typeは &
で拡張できる
// 明示的な数値
enum HttpStatus {
OK = 200,
NOT_FOUND = 404,
SERVER_ERROR = 500
}
let response = HttpStatus.OK;
console.log(response); // 200
console.log(HttpStatus[200]); // "OK"
// 自動割り当て
enum StandType {
STAR_PLATINUM, // 0
THE_WORLD, // 1
CRAZY_DIAMOND, // 2
}
let jotaro = StandType.STAR_PLATINUM;
console.log(jotaro); // 0
console.log(StandType[0]); // "STAR_PLATINUM"
enum LogLevel {
ERROR = "error",
WARN = "warn",
INFO = "info"
}
function log(level: LogLevel, message: string) {
console.log(`[${level}] ${message}`);
}
log(LogLevel.ERROR, "something broke");
enum Permission {
READ = 1, // 0001
WRITE = 2, // 0010
EXECUTE = 4, // 0100
ALL = READ | WRITE | EXECUTE // 0001 | 0010 | 0100 = 0111 = 7
}
function hasPermission(user: number, perm: Permission) {
return (user & perm) === perm;
}
hasPermission(5, Permission.WRITE) // 5 = 0101 に WRITE(0010) は含まれない → false
// enum
enum Color { RED, GREEN, BLUE }
// const assertion
const Color2 = {
RED: 'red',
GREEN: 'green',
BLUE: 'blue'
} as const;
type ColorType = typeof Color2[keyof typeof Color2];
let userName; // 未初期化
console.log(userName); // undefined
function processUser(name?: string) {
console.log(name); // 引数を渡さなければ undefined
}
const user: any = { name: "dio" };
console.log(user.age); // 存在しないプロパティは undefined
undefined
は「まだ設定されていない」状態を表す。
// APIレスポンス
interface UserProfile {
name: string;
avatar: string | null; // 意図的にnullを許可
}
// データベースクエリ
const findUser = (id: number): User | null => {
// ユーザーが見つからない場合はnullを返す
return users.find(u => u.id === id) || null;
};
// 初期化時の明示的な空状態
let selectedStand: Stand | null = null;
null
は「意図的に空である」ことを明示する。
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"strictNullChecks": true
}
}
null
/undefined
を型に明示的に含めないとエラーstrictNullChecks: false
let standName: string = null; // OK(危険)
strictNullChecks: true
let standName: string = null; // ✕ エラー
// 正しい書き方
let standName: string | null = null; // ✓ OK
let standUser: string | undefined; // ✓ OK
安全なプロパティアクセス
interface Stand {
name: string;
user?: {
name: string;
age?: number;
};
}
const theWorld: Stand = { name: "The World" };
// 従来の書き方
const userName = theWorld.user && theWorld.user.name;
const userAge = theWorld.user && theWorld.user.age;
// Optional chaining
const userName = theWorld.user?.name;
const userAge = theWorld.user?.age;
null
/undefined
のときのみデフォルト値を設定
// 従来の方法(問題あり)
const displayName = user.name || "dio"; // 空文字でもdioになる
// Nullish coalescing(正確)
const displayName2 = user.name ?? "dio"; // null/undefinedのときのみdio
// 実用例
const config = {
timeout: userSettings.timeout ?? 5000,
retries: userSettings.retries ?? 3,
endpoint: userSettings.endpoint ?? "https://api.example.com"
};
||
演算子と違って、0
や空文字などのfalsyな値は通す。
function processStand(stand: Stand | null) {
if (stand === null) { // 型ガード
console.log("standが見つかりません");
return;
}
// この時点でstandはStand型として扱われる
console.log(`stand名: ${stand.name}`);
// ネストした型ガード
if (stand.user) {
console.log(`user名: ${stand.user.name}`);
}
}
「絶対null/undefinedじゃない」と断言
function getStandPower(stand?: Stand) {
// 危険:standがundefinedの可能性
return stand!.name.toUpperCase();
}
// より安全な書き方
function getStandPowerSafe(stand?: Stand) {
if (!stand) {
throw new Error("standが必要です");
}
return stand.name.toUpperCase();
}
null
:意図的な空値、undefined
:未初期化strictNullChecks
は必須設定?.
で安全なアクセス、??
でデフォルト値!
の使用は可能な限り避けること「この値は絶対この型だ」とTypeScriptに伝える
// 2つの書き方
const userInput = document.getElementById("name") as HTMLInputElement;
const userInput2 = <HTMLInputElement>document.getElementById("name");
// APIレスポンスの型アサーション
const apiResponse = await fetch("/api/user");
const userData = await apiResponse.json() as User;
const maybeUser = getData() as User;
console.log(maybeUser.name); // 実行時エラーの可能性
function isUser(obj: any): obj is User {
return obj && typeof obj.name === "string";
}
const data = getData();
if (isUser(data)) {
console.log(data.name); // 安全
}
値を変更不可能な定数として扱う
// 通常の配列
const stands = ["za warudo", "killer queen"]; // string[]
// const assertion
const standsConst = ["za warudo", "killer queen"] as const;
// readonly ["za warudo", "killer queen"]
// オブジェクトの場合
const config = {
timeout: 5000,
retries: 3
} as const;
// { readonly timeout: 5000; readonly retries: 3 }
// 危険な例
const badCast = "jotaro" as any as number;
console.log(badCast * 2); // 実行時エラー
// 適切な例
const jsonData = JSON.parse(response) as User;
const user = {
score: 0,
avatar: "", // 空の文字列 - ユーザーがアバターを削除した
backup: null as string | null // 最初はnull、あとで文字列(URLなど)を入れるための型指定
};
const result1 = user.score || 100;
const result2 = user.score ?? 100;
const result3 = user.avatar || "default.jpg";
const result4 = user.avatar ?? "default.jpg";
const result5 = user.backup || "fallback.jpg";
const result6 = user.backup ?? "fallback.jpg";
console.log(result1, result2, result3, result4, result5, result6);
||
は falsy値 (0, "") で fallback するが、??
は
null/undefined のみ。
||
は "default.jpg"、??
は "" を保持
オブジェクト指向の基本構造
JavaScriptのオブジェクト指向の基本メカニズム
prototype
チェーンを持つ
const animal = {
speak: function() {
return "何か音を出す";
}
};
const dog = Object.create(animal);
dog.bark = function() {
return "わんわん!";
};
console.log(dog.speak()); // "何か音を出す" ← animalから継承
console.log(dog.bark()); // "わんわん!" ← 自分のメソッド
class Animal {
public function speak() {
return "音を出す";
}
}
class Dog extends Animal {
public function bark() {
return "わんわん";
}
}
設計図(クラス) → インスタンス
function Animal() {}
Animal.prototype.speak = function() {
return "音を出す";
};
function Dog() {}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.bark = function() {
return "わんわん";
};
オブジェクト → オブジェクト
class Dog {
constructor(name) {
this.name = name;
}
bark() {
return "わんわん";
}
}
function Dog(name) {
this.name = name;
}
Dog.prototype.bark = function() {
return "わんわん";
};
this
の動作を理解するためclass
はプロトタイプの「見た目をよくしたもの」
class Stand {
name: string; // クラスプロパティ
constructor(name: string, public power: number) { // public引数はプロパティの自動宣言+初期化
this.name = name; // コンストラクタでプロパティを初期化
}
attack(): string { // クラスメソッド
return `${this.name}で攻撃!パワー: ${this.power}`;
}
}
const starPlatinum = new Stand("スタープラチナ", 100);
console.log(starPlatinum.attack()); // "スタープラチナで攻撃!パワー: 100"
console.log(starPlatinum.power); // 100 (public引数で自動生成)
constructor(public prop: type)
は this.prop = prop
+ プロパティ宣言の省略形
class Character {
public name: string; // どこからでもアクセス可能
private hp: number; // クラス内のみ
protected level: number; // 継承先でも使用可能
constructor(name: string, hp: number, level: number) {
this.name = name;
this.hp = hp;
this.level = level;
}
private heal(): void {
this.hp += 20;
}
}
public
。private
は外部からアクセス不可、protected
は継承先でのみ使用可能。
class Villain extends Character {
private evilPlan: string;
constructor(name: string, hp: number, level: number, plan: string) {
super(name, hp, level); // 親クラスのコンストラクタを呼び出し
this.evilPlan = plan;
}
public getStatus(): string { // メソッドオーバーライド
return `${super.getStatus()}, 計画: ${this.evilPlan}`;
}
protected useSpecialAbility(): void {
console.log(`${this.name}が特殊能力を使用!レベル${this.level}の力で!`);
}
}
const dio = new Villain("ディオ", 200, 50, "世界征服");
console.log(dio.getStatus()); // "ディオ: HP 200, Lv.50, 計画: 世界征服"
// dio.useSpecialAbility(); // ✕ エラー: protected
super()
: 親のコンストラクタ/メソッド呼び出し、protected
は継承先でのみ使用可能
class ClubMember {
readonly membershipId: string;
private static memberCount: number = 0;
static readonly clubName: string = "雛見沢分校";
constructor(public name: string, membershipId: string) {
this.membershipId = membershipId;
ClubMember.memberCount++; // private staticもクラス内からアクセス可能
}
static getClubInfo(): string {
return `${ClubMember.clubName}: ${ClubMember.memberCount}人`; // staticプロパティはクラス名でアクセス
}
}
const keiichi = new ClubMember("前原圭一", "001");
const rena = new ClubMember("竜宮レナ", "002");
console.log(ClubMember.getClubInfo()); // "雛見沢分校: 2人"
// keiichi.membershipId = "003"; // ✕ エラー: readonly
// console.log(ClubMember.memberCount); // ✕ エラー: private static
readonly
: 初期化後変更不可、static
:
クラス自体に属する(インスタンス不要)、private static
: クラス内でのみアクセス可能
class Pokemon {
private _hp: number;
constructor(public name: string, private _maxHp: number) {
this._hp = _maxHp;
}
get hp(): number { return this._hp; }
set hp(value: number) {
this._hp = Math.max(0, Math.min(value, this._maxHp));
}
get healthPercentage(): number {
return Math.round((this._hp / this._maxHp) * 100);
}
}
const pikachu = new Pokemon("ピカチュウ", 100);
pikachu.hp = 150; // setter (maxHpに制限される)
console.log(pikachu.hp); // 100
console.log(pikachu.healthPercentage); // 100 (read-only)
get
: プロパティアクセス時の処理、set
: 代入時の検証・変換
abstract class Weapon {
constructor(public name: string, public damage: number) {}
abstract attack(): string; // 子クラスで実装必須
public getInfo(): string { // 共通の実装
return `${this.name} (威力: ${this.damage})`;
}
}
class Sword extends Weapon {
attack(): string {
return `${this.name}で斬りつけた!${this.damage}のダメージ!`;
}
}
// const weapon = new Weapon("", 0); // ✕ エラー: 抽象クラスはインスタンス化不可
const sword = new Sword("エクスカリバー", 50);
console.log(sword.attack()); // "エクスカリバーで斬りつけた!50のダメージ!"
abstract
: インスタンス化不可、子クラスでの実装強制。共通処理と個別実装を分離
class
は本質的にJavaScriptのプロトタイプベース継承の糖衣構文
コードの整理と再利用
export
でモジュール外部に公開。
// stands.ts
export class Stand {
constructor(public name: string, public power: number) {}
}
export const STAND_TYPES = {
CLOSE_RANGE,
LONG_RANGE
} as const;
// デフォルトエクスポート
export default class StandUser {
constructor(public name: string, public stand: Stand) {}
}
default export
は1つまで、named export
は複数可能
import
でモジュールを読み込み。
// main.ts
import StandUser, { Stand, STAND_TYPES } from './stands';
import { Stand as StandClass } from './stands';
import * as StandModule from './stands';
const jotaro = new StandUser("承太郎", new Stand("スタープラチナ", 100));
const dio = new StandModule.default("DIO", new StandModule.Stand("ザ・ワールド", 95));
* as
で全てを名前空間オブジェクトとして取得。default export
は ModuleName.default
でアクセス
型を「後で決める」仕組み
// ジェネリクスなしだと...
function getFirstStand(stands: Stand[]): Stand { return stands[0]; }
function getFirstFragment(fragments: Fragment[]): Fragment { return fragments[0]; }
// 型ごとに同じ関数を作る必要がある
// ジェネリクスがあると
function getFirst<T>(items: T[]): T {
return items[0];
}
// 一つの関数で全てに対応
const firstStand = getFirst(stands); // Stand型
const firstFragment = getFirst(fragments); // Fragment型
// 関数のジェネリクス
function identity<T>(arg: T): T {
return arg;
}
// 使い方
const result1 = identity<string>("za warudo"); // 明示的に型指定
const result2 = identity("muda muda"); // 型推論で自動判定
// 型が保持される
console.log(result1.length); // OK: string のメソッドが使える
console.log(result2.toUpperCase()); // OK: 型推論で string と判定
<T>
が型パラメータ。関数呼び出し時に具体的な型が決まる。// 普段書いているこれもジェネリクス
const stands: string[] = ["star platinum", "the world"];
const numbers: number[] = [1, 2, 3];
// 実は以下と同じ意味
const stands: Array<string> = ["star platinum", "the world"];
const numbers: Array<number> = [1, 2, 3];
// Promise も同じ
const response: Promise<string> = fetch("/api").then(r => r.text());
Array<T>
, Promise<T>
などはジェネリクスの典型例。<T>
の部分に具体的な型を入れて使う。もう慣れ親しんでいる!
// 複数の型パラメータ - 関連する異なる型を扱う時
function createPair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
// ユーザーとそのメインスタンドをペアにする
const jotaro = createPair("jotaro", { name: "star platinum", power: 95 });
// [string, Stand]
// APIレスポンスとメタデータをペアにする
const response = createPair(userData, { timestamp: Date.now() });
// [User, { timestamp: number }]
<T, U>
は慣例的な名前。<TKey, TValue>
や
<TData, TMeta>
など意味のある名前でもOK。
// any 版
function processData(data: any): any {
return data;
}
// generics 版
function processData<T>(data: T): T {
return data;
}
const result = processData("jotaro");
// ジェネリクスを使った汎用的な関数
function findByName<T extends { name: string }>(items: T[], name: string): T | undefined {
return items.find(item => item.name === name);
}
const stands: Stand[] = [
{ name: "star platinum", user: "jotaro", power: 95 },
{ name: "the world", user: "dio", power: 90 }
];
const weapons: Weapon[] = [
{ name: "arrow", damage: 50, type: "ranged" },
{ name: "requiem arrow", damage: 100, type: "ranged" }
];
// 型安全に使える
const starPlatinum = findByName(stands, "star platinum"); // Stand | undefined
const arrow = findByName(weapons, "arrow"); // Weapon | undefined
extends
で型に制約を加えることができる。name
プロパティを持つ型のみ受け入れる。
interface ApiResponse<T> {
data: T;
status: "success" | "error";
}
// 汎用的なAPI関数
async function fetchData<T>(url: string): Promise<ApiResponse<T>> {
const response = await fetch(url);
return response.json();
}
// 使用時に型が決まる
const standsResponse = await fetchData<Stand[]>("/api/stands");
const userResponse = await fetchData<User>("/api/user");
// 型安全にアクセス
if (standsResponse.status === "success") {
standsResponse.data.forEach(stand => {
console.log(stand.name); // Stand型として扱われる
});
}
data
の型だけ変えて再利用できる。
function findByPower<T extends { power: number }>(
items: T[],
minPower: number
): T[] {
return items.filter(item => item.power >= minPower);
}
extends { power: number }
は「power プロパティを持つ型」の制約。
function echo<T>(value: T): T {
return value;
}
const result1 = echo("za warudo");
const result2 = echo<string>("muda muda");
const result3 = echo(42);
console.log(typeof result1, typeof result2, typeof result3);
any
を使わずに型チェックを維持動的なオブジェクト構造を扱う方法
// プロパティ名が事前に分からない場合
interface StringDictionary {
[key: string]: string;
}
interface NumberDictionary {
[key: string]: number;
length: number; // ok, lengthはnumber型
// name: string; // error, インデックスシグネチャと競合
}
// 複数のインデックスシグネチャ
interface MixedDictionary {
[key: string]: any;
[key: number]: string; // 数値キーは文字列キーに割り当て可能である必要がある
}
// ✕ 非推奨: 型安全なし
interface Config {
[key: string]: any;
}
// ✓ 推奨: 型明示で安全
interface Config {
apiUrl: string;
timeout: number;
retries?: number;
}
現実的な判断: 必要な時もあるが、まず具体的な型定義を検討する
型レベルでの条件分岐
// T extends U ? X : Y の形
// 「TがUを継承/拡張できるか?」で分岐
type IsString<T> = T extends string ? true : false;
type Test1 = IsString<string>; // true
type Test2 = IsString<number>; // false
type Test3 = IsString<"hello">; // true(文字列リテラル型はstring型に含まれる)
extends
は「継承」ではなく「含まれる/代入可能」の意味。T extends string
は「Tがstring型に代入可能か?」を判定。
// null/undefinedを除外する型
type NonNull<T> = T extends null | undefined ? never : T;
type SafeString = NonNull<string | null>; // string
type SafeNumber = NonNull<number | undefined>; // number
// 配列かどうかを判定
type IsArray<T> = T extends unknown[] ? true : false;
type ArrayCheck1 = IsArray<string[]>; // true
type ArrayCheck2 = IsArray<string>; // false
TypeScriptが提供する便利な型変換ツール
interface User {
id: number;
name: string;
email: string;
isActive: boolean;
}
// Partial<T> - 全プロパティを省略可能に
type UserUpdate = Partial<User>; // { id?: number; name?: string; email?: string; isActive?: boolean; }
// Pick<T, K> - 特定のプロパティだけ選択
type UserProfile = Pick<User, "name" | "email">; // { name: string; email: string; }
// Omit<T, K> - 特定のプロパティを除外
type UserWithoutId = Omit<User, "id">; // { name: string; email: string; isActive: boolean; }
// Record<K, T> - キーと値の型を指定してオブジェクト型作成
type UserStatus = Record<string, boolean>; // { [key: string]: boolean; }
// API更新用(一部のフィールドのみ)
function updateUser(id: number, updates: Partial<User>) {
// name だけ更新、email だけ更新などが可能
}
// 公開用プロフィール(idを隠す)
function getPublicProfile(user: User): Omit<User, "id"> {
const { id, ...publicData } = user;
return publicData;
}
// 設定値の管理
const config: Record<string, string> = {
apiUrl: "https://api.example.com",
theme: "dark"
};
今日学んだ内容を実際に使ってみよう!
以下の User
から新しい型を作成してください
interface User {
id: number;
name: string;
email: string;
password: string;
isActive: boolean;
}
PublicUser
: password
を除いた型UserUpdate
: id
を除き、全て省略可能な型UserSummary
: id
と name
のみの型ユーティリティ型を組み合わせて必要な型を作成
// password を除いた型
type PublicUser = Omit<User, "password">;
// id を除き、全て省略可能
type UserUpdate = Partial<Omit<User, "id">>;
// 指定されたプロパティのみ
type UserSummary = Pick<User, "id" | "name">;
以下の関数をジェネリクスを使って型安全に書き直してください
// 現在の実装(型が不十分)
function transformArray(items: any[], transformer: (item: any) => any): any[] {
return items.map(transformer);
}
// 使用例
const numbers = [1, 2, 3];
const strings = transformArray(numbers, (n) => n.toString());
const lengths = transformArray(["hello", "world"], (s) => s.length);
<T, U>
ジェネリクスで入力と出力の型を関連付ける
function transformArray<T, U>(items: T[], transformer: (item: T) => U): U[] {
return items.map(transformer);
}
// 使用例 - 型推論が正しく動作
const numbers = [1, 2, 3];
const strings = transformArray(numbers, (n) => n.toString()); // string[]
const lengths = transformArray(["hello", "world"], (s) => s.length); // number[]
// TypeScriptが型エラーを検出
// transformArray(numbers, (n) => n.charAt(0)); // Error: charAt は number にない
<T, U>
を使って入力と出力の型を関連付けているReact基礎①: Reactとは、JSX、コンポーネント、Propsの使い方