[TypeScript]メソッドデコレータ

class Articke{
    public name:string;
    constructor(name:string){
        this.name = name;
    }
    @logged
    showName(){
        console.log(this.name);
    }
}
const art:Articke = new Articke("あいうえお");
art.showName();
setTimeout(art.showName,1000);

function logged(originalMethod:any,context:any){
    function loggedMethod(this:any,...args:any[]){
        console.log(`${context.name}メソッドスタート`);
        const result = originalMethod.call(this,...args);
        console.log(`${context.name}メソッド終了`);
        return result;
    }
    return loggedMethod;
}


 
出力

showNameメソッドスタート
あいうえお
showNameメソッド終了
showNameメソッドスタート
undefined
showNameメソッド終了

 
thisをインスタンスに束縛してthis.nameがundefinedになるのを回避する

class Articke{
    public name:string;
    constructor(name:string){
        this.name = name;
    }
    @bound
    @logged
    showName(){
        console.log(this.name);
    }
}
const art:Articke = new Articke("あいうえお");
art.showName();
setTimeout(art.showName,1000);

function logged(originalMethod:any,context:any){
    function loggedMethod(this:any,...args:any[]){
        console.log(`${context.name}メソッドスタート`);
        const result = originalMethod.call(this,...args);
        console.log(`${context.name}メソッド終了`);
        return result;
    }
    return loggedMethod;
}

//setTimeout(art.showName,1000);のthis.nameがundefinedになるのを回避
function bound(_originalMethod:any,context:any){
    context.addInitializer(function(this:any){
        this[context.name] = this[context.name].bind(this);
    });
}

 
出力

showNameメソッドスタート
あいうえお
showNameメソッド終了
showNameメソッドスタート
あいうえお
showNameメソッド終了

 
 
型を設定すると…


function logged<This,Args extends any[],Return>(
    originalMethod:(this:This,...args:Args) => Return,
    context:ClassMethodDecoratorContext<This,(this:This,...args:Args) => Return>
){
    function loggedMethod(this:This,...args:Args):Return{
        console.log(`${context.name.toString()} start`);
        const result = originalMethod.call(this,...args);
        console.log(`${context.name.toString()} finish`);
        return result;
    }
    return loggedMethod;
}
function bound<This,Args extends any[],Return>(
    _originalMethod:(this:This,...args:Args) => Return,
    context:ClassMethodDecoratorContext<This,(this:This,...args:Args) => Return>
){
    context.addInitializer(function(this:any){
        this[context.name] = this[context.name].bind(this);
    });
}

class Article{
    protected name:string;
    constructor(name:string){
        this.name = name;
    }
    @bound
    @logged
    showData():void{
        console.log(this.name);
    }
}

const article = new Article("John Smith");
article.showData();
setTimeout(article.showData,1000);

 
出力

showData start
John Smith
showData finish
//1秒待つ
showData start
John Smith
showData finish

[TypeScript]ユーティリティ型

Partial<T>
プロパティをオプショナルにする

interface User {
  id: number;
  name: string;
  age: number;
}

type PartialUser = Partial<User>;

const user: PartialUser = { name: "Taro" }; // OK(id, age なしでもOK)

 
Required<T>
プロパティを必須にする

interface User {
  id: number;
  name?: string;
  age?: number;
}

type RequiredUser = Required<User>;

const user: RequiredUser = { id: 1, name: "Taro", age: 25 }; // OK
// const user2: RequiredUser = { id: 1 }; // エラー(name, age が必須)

 
Readonly<T>
プロパティを読み取り専用にする

interface User {
  id: number;
  name: string;
}

const user: Readonly<User> = { id: 1, name: "Taro" };

// user.name = "Jiro"; // エラー(再代入不可)

 
Pick<T, K>
特定のプロパティのみを取り出す

interface User {
  id: number;
  name: string;
  age: number;
}

type PickedUser = Pick<User, "id" | "name">;

const user: PickedUser = { id: 1, name: "Taro" }; // OK(ageなし)

 
Omit<T, K>
特定のプロパティを除外する

interface User {
  id: number;
  name: string;
  age: number;
}

type OmittedUser = Omit<User, "age">;

const user: OmittedUser = { id: 1, name: "Taro" }; // OK(ageなし)

 
Record<K, T>
オブジェクトのキーと値の型を指定
キー(K)の型と値(T)の型を固定できる。

type UserRoles = Record<"admin" | "user" | "guest", number>;

const roles: UserRoles = {
  admin: 1,
  user: 2,
  guest: 3,
};

 
Extract<T, U>
Tの中からUに一致する型を抽出

type Status = "active" | "inactive" | "banned";
type ActiveStatus = Extract<Status, "active" | "banned">;

const status1: ActiveStatus = "active"; // OK
const status2: ActiveStatus = "banned"; // OK
// const status3: ActiveStatus = "inactive"; // エラー(除外されている)

 
Exclude<T, U>
Tの中からUに一致する型を除外

type Status = "active" | "inactive" | "banned";
type InactiveStatus = Exclude<Status, "active">;

const status1: InactiveStatus = "inactive"; // OK
const status2: InactiveStatus = "banned"; // OK
// const status3: InactiveStatus = "active"; // エラー(除外されている)

 
NonNullable<T>
nullとundefinedを除外

type Data = string | number | null | undefined;
type NonNullableData = NonNullable<Data>;

const value1: NonNullableData = "hello"; // OK
const value2: NonNullableData = 42; // OK
// const value3: NonNullableData = null; // エラー
// const value4: NonNullableData = undefined; // エラー

 
ReturnType<T>
関数の戻り値の型を取得

function getUser() {
  return { id: 1, name: "Taro" };
}

type UserType = ReturnType<typeof getUser>;

const user: UserType = { id: 2, name: "Jiro" }; // OK

 
 

ユーティリティ型 説明
Partial<T> すべてのプロパティをオプショナルにする
Required<T> すべてのプロパティを必須にする
Readonly<T> すべてのプロパティを変更不可にする
Pick<T, K> 指定したプロパティのみ抽出
Omit<T, K> 指定したプロパティを除外
Record<K, T> キー K に対して値 T を割り当てる
Extract<T, U> U に一致する型のみを抽出
Exclude<T, U> U に一致する型を除外
NonNullable<T> nullundefined を除外
ReturnType<T> 関数の戻り値の型を取得

[TypeScript]ジェネリック型エイリアス

type Box<T> = {
  value: T;
};

const numberBox: Box<number> = { value: 100 };
const stringBox: Box<string> = { value: "Hello" };

console.log(numberBox.value); // 100
console.log(stringBox.value); // "Hello"

 
配列

type List<T> = T[];

const numbers: List<number> = [1, 2, 3];
const strings: List<string> = ["a", "b", "c"];

console.log(numbers); // [1, 2, 3]
console.log(strings); // ["a", "b", "c"]

 
オブジェクト型の拡張

type Identifiable<T extends { id: number }> = {
  data: T;
};

const user: Identifiable<{ id: number; name: string }> = {
  data: { id: 1, name: "Taro" },
};

console.log(user.data.name); // "Taro"

 
複数の型パラメータ

type Pair<T, U> = {
  first: T;
  second: U;
};

const pair: Pair<number, string> = { first: 1, second: "One" };
console.log(pair.first); // 1
console.log(pair.second); // "One"

 
関数

type Func<T, U> = (arg: T) => U;

const toString: Func<number, string> = (num) => `Number: ${num}`;
console.log(toString(10)); // "Number: 10"

 
デフォルト型

type ResponseData<T = string> = {
  data: T;
};

const res1: ResponseData = { data: "Success" }; // Tはstringとして扱われる
const res2: ResponseData<number> = { data: 200 }; // 明示的にnumberを指定

console.log(res1.data); // "Success"
console.log(res2.data); // 200

 
条件付き型

type IsString<T> = T extends string ? "文字列" : "その他";

type Result1 = IsString<string>;  // "文字列"
type Result2 = IsString<number>;  // "その他"

let test1:Result1 = "文字列";//"文字列"以外はエラー
let test2:Result2 = "その他";//"その他"以外はエラー
console.log(test1);
console.log(test2);

 
型制約

class DataStorage<T extends number | string>{
    private items:T[] = [];
}

[TypeScript]ジェネリッククラス

class Box<T> {
  private value: T;

  constructor(value: T) {
    this.value = value;
  }

  getValue(): T {
    return this.value;
  }

  setValue(newValue: T): void {
    this.value = newValue;
  }
}

// 使用例
const numberBox = new Box<number>(10);
console.log(numberBox.getValue()); // 10

const stringBox = new Box<string>("Hello");
console.log(stringBox.getValue()); // "Hello"

 
複数の型パラメータ

class Pair<T, U> {
  constructor(private first: T, private second: U) {}

  getFirst(): T {
    return this.first;
  }

  getSecond(): U {
    return this.second;
  }
}

// 使用例
const pair1 = new Pair<number, string>(1, "One");
console.log(pair1.getFirst()); // 1
console.log(pair1.getSecond()); // "One"

const pair2 = new Pair<boolean, string>(true, "True");
console.log(pair2.getFirst()); // true
console.log(pair2.getSecond()); // "True"

 
ジェネリック型Tに特定の型を継承させて型の制約を設ける

class Calculator<T extends number> {
  private value: T;

  constructor(value: T) {
    this.value = value;
  }

  add(num: T): number {
    return this.value + num;
  }
}

// 使用例
const calc = new Calculator(10);
console.log(calc.add(5)); // 15

// const invalidCalc = new Calculator("Hello"); // エラー: stringはnumberを継承していない

 
クラスとインターフェースの組み合わせ

interface Identifiable {
  id: number;
}

class Repository<T extends Identifiable> {
  private items: T[] = [];

  add(item: T): void {
    this.items.push(item);
  }

  findById(id: number): T | undefined {
    return this.items.find(item => item.id === id);
  }
}

// 使用例
interface User {
  id: number;
  name: string;
}

const userRepo = new Repository<User>();
userRepo.add({ id: 1, name: "Taro" });
userRepo.add({ id: 2, name: "Jiro" });

console.log(userRepo.findById(1)); // { id: 1, name: "Taro" }
console.log(userRepo.findById(3)); // undefined

 
デフォルト型を指定

class DefaultBox<T = string> {
  private value: T;

  constructor(value: T) {
    this.value = value;
  }

  getValue(): T {
    return this.value;
  }
}

// 使用例
const defaultBox = new DefaultBox("Hello"); // string型を推論
console.log(defaultBox.getValue()); // "Hello"

const numberBox = new DefaultBox<number>(100); // 明示的にnumber型を指定
console.log(numberBox.getValue()); // 100

 
コンストラクタで型推論

class DataStorage<T = number>{
    private items:T[] = [];
    constructor(initItems?:T[]){
        if(initItems){
            this.items.push(...initItems);
        }
    }
}
let stringStorage = new DataStorage(["A","B"]);

 
ジェネリッククラスの継承

class DataStorage<T>{
    private items:T[] = [];
}
class DataStorageSub extends DataStorage<string>{
    logAllItems():void{
        console.log([...this.items]);
    }
}
class DataStorageSub<T> extends DataStorage<T>{
    logAllItems():void{
        console.log([...this.items]);
    }
    getLastValue():T{
        return this.items[this.items.length-1];
    }
}

[JavaScript]Symbolをオブジェクトのプロパティキーとして使用

Symbol は組み込みオブジェクトであり、コンストラクターは一意であることが保証されているシンボルプリミティブ(シンボル値または単にシンボル)を返します。
シンボルは、他のコードがオブジェクトに追加する可能性のあるキーと衝突しないように、また、他のコードがオブジェクトにアクセスするために通常使用するメカニズムから隠されるるように、一意のプロパティキーをオブジェクトに追加するためによく使用されます。

const mySymbol = Symbol("myKey");

const obj = {
  [mySymbol]: "シンボルの値",
  normalKey: "通常の値"
};

console.log(obj[mySymbol]); // "シンボルの値"
console.log(obj.normalKey); // "通常の値"

// シンボルキーは通常のループで取得できない
for (let key in obj) {
  console.log(key); // "normalKey" だけが表示される(Symbolキーは表示されない)
}

console.log(Object.keys(obj)); // ["normalKey"]
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(myKey)]

[TypeScript]ジェネリック関数

引数や返り値の型を可変にする

function getLastValue<T>(array:T[]):T{
    return array[array.length-1];
}
const numbers:number[] = [1,2,3,4,5];
const strings:string[] = ["A","B","C","D"];

console.log(getLastValue(numbers));
console.log(typeof getLastValue(numbers));
console.log(getLastValue(strings));
console.log(typeof getLastValue(strings));
console.log(getLastValue<number>(numbers));//型推論に頼らず明示的に返り値の型を指定する場合
console.log(typeof getLastValue<number>(numbers));

 
出力

5
number
D
string
5
number

 
引数が複数

function getPairArray<T,U>(first:T,second:U):[T,U]{
    return [first,second];
}
const pair1 = getPairArray(1,"A");
const pair2 = getPairArray(2,3);

console.log(pair1);
console.log(pair2);

 
出力

[ 1, 'A' ]
[ 2, 3 ]

 
デフォルト型を指定

function getPairArray<T = number,U = string>(first:T,second:U):[T,U]{
    return [first,second];
}

const pair1 = getPairArray(1,"A");
const pair2 = getPairArray(2,3);
const pair3 = getPairArray("B",4);

console.log(pair1);
console.log(pair2);
console.log(pair3);

 
デフォルト型を指定しても型推論が優先される

[ 1, 'A' ]
[ 2, 3 ]
[ 'B', 4 ]

[TypeScript]ユーザ定義型ガード

value is stringを指定しないと正しく動作しない

function isString(value:unknown):value is string{
    return typeof value === "string";
}
function showData(value:number|string){
    if(isString(value)){
        console.log(value.toUpperCase());
    }else{
        console.log(value.toFixed(2));
    }
}

[TypeScript]型の絞り込み

typeof

function showValue(value:string | number){
    if(typeof value === "string"){
        console.log(value.toUpperCase());
    }else{
        console.log(value.toFixed(2));
    }
}

 
instanceof

class John{
    dance(){
        console.log("let's dance!");
    }
}
class Jeff{
    calculate(){
        console.log("1+1=5");
    }
}
function execute(person:John|Jeff){
    if(person instanceof John){
        person.dance();
    }else{
        person.calculate();
    }
}

 
タグ付きユニオン型

interface Circle{
    type:"circle";
    radius:number;
}
interface Square{
    type:"square";
    width:number;
}
type Shape = Circle | Square;
function showData(shape:Shape){
    switch(shape.type){
        case "circle":
            console.log("circle !");
            break;
        case "square":
            console.log("square !");
            break;
    }
}

[TypeScript]interface

// Shape インターフェースの定義
interface Shape {
  color: string;
  getArea(): number; // メソッドの型のみ定義
  describe(): void;
}

// Circle クラス
class Circle implements Shape {
  constructor(public color: string, private radius: number) {}

  getArea(): number {
    return Math.PI * this.radius ** 2;
  }

  describe(): void {
    console.log(`This is a ${this.color} circle.`);
  }
}

// Rectangle クラス
class Rectangle implements Shape {
  constructor(public color: string, private width: number, private height: number) {}

  getArea(): number {
    return this.width * this.height;
  }

  describe(): void {
    console.log(`This is a ${this.color} rectangle.`);
  }
}

// インスタンス作成
const circle = new Circle("red", 10);
console.log(circle.getArea()); // 314.159...
circle.describe(); // This is a red circle.

const rectangle = new Rectangle("blue", 5, 8);
console.log(rectangle.getArea()); // 40
rectangle.describe(); // This is a blue rectangle.

特徴 interface abstract class
インスタンス化 不可 不可
プロパティ・メソッドの型定義 型のみ 型+実装
コンストラクタ なし あり
複数の継承 可能(多重継承OK) 不可能(単一継承のみ)

 

複数のinterfaceをimplementsするクラス

// 移動可能なオブジェクトのインターフェース
interface Movable {
  speed: number;
  move(): void;
}

// 描画可能なオブジェクトのインターフェース
interface Drawable {
  color: string;
  draw(): void;
}

// CarクラスがMovableとDrawableを両方実装
class Car implements Movable, Drawable {
  constructor(public speed: number, public color: string) {}

  move(): void {
    console.log(`The car is moving at ${this.speed} km/h.`);
  }

  draw(): void {
    console.log(`Drawing a ${this.color} car.`);
  }
}

// インスタンス作成
const myCar = new Car(100, "red");

myCar.move();  // The car is moving at 100 km/h.
myCar.draw();  // Drawing a red car.

 
オブジェクトに使用

interface User {
  name: string;
  age: number;
}

const user: User = {
  name: "Taro",
  age: 25,
};

console.log(user.name); // "Taro"
console.log(user.age);  // 25

interface User {
  name: string;
  age?: number; // ageは省略可能
}

const user1: User = { name: "Taro" }; // OK
const user2: User = { name: "Jiro", age: 30 }; // OK

 
関数に使用

interface Greeting {
  (name: string): string;
}

const sayHello: Greeting = (name) => `Hello, ${name}!`;

console.log(sayHello("John")); // "Hello, John!"

 
配列に使用

interface NumberArray {
  [index: number]: number;
}

const numbers: NumberArray = [1, 2, 3, 4];

console.log(numbers[0]); // 1

 
キャストする

interface Car {
  brand: string;
  model: string;
}

const myCar = {
  brand: "Toyota",
  model: "Corolla",
  year: 2023, // 追加のプロパティ
} as Car; // `year`は無視される

console.log(myCar.brand); // "Toyota"
console.log(myCar.model); // "Corolla"