【TS】TypeScript ~ クラス / インターフェイス ~

■ はじめに

最近、Pulumi で TypeScript を使ってて、
その際に、クラス / インターフェイス を使いたかったのでメモ。

目次

【0】実行環境
【1】クラス
 1)アクセス修飾子
 2)継承
 3)抽象クラス・メソッド - abstract
 4)静的メソッド (static method)
 5)別ファイルに定義する
【2】インターフェイス
【3】トラブル「TypeError: Class extends value undefined is not a constructor or null」

【0】実行環境

* ブラウザで実行できる

https://www.typescriptlang.org/play

【1】クラス

// クラス定義
class Person {
  // プロパティ
  private name: string

  // コンストラクタ
  constructor(name: string) {
    this.name = name
  }
  // メソッド
  sayHello(): string {
    return `Hello, ${this.name}!!`;
  }
}

// 呼び出し例
const person: Person = new Person("Mike");
console.log(person.sayHello()); // Hello, Mike!!

1)アクセス修飾子

[1] private
[2] protected
[3] public (デフォルト)

2)継承

// extends で 継承
class Employee extends Person {
  constructor(id: number, name: string) {
    super(name)
    this.id = id
  }
}

3)抽象クラス・メソッド - abstract

https://qiita.com/suema0331/items/374c0757aa00b37d98bd

使用上の注意

* abstractメソッドは、abstractクラスからしか使用できない

サンプル

abstract class BaseDeployment {
  abstract deploy(): void;
}

4)静的メソッド (static method)

class Connection {
  public readonly string url1;
  public readonly string url2;
  constructor(domain: string) {
    this.url1 = Connection.getUrl(domain)
    this.url2 = Connection.getUrl(domain, 'test1', 'path2/path3')
  }
  static getUrl(domain: string, ...params: string): string {
    return `https://${domain}/` + params.join('/')
  }
}
const connection = new Connection('hello_world.com')
// https://hello_world.com/
console.log(connection.url1)
// https://hello_world.com/test1/path2/path3
console.log(connection.url2)

5)別ファイルに定義する

クラスファイル「helloWorld.ts」

// クラス定義
export class HelloWorld {
  readonly val: string
  constructor(
    public name: string,
    {
      val
    }: HelloArgs,
  ) {
    this.name = name
  }

  // メソッド
  sayHello(): string {
    return `Hello, ${this.name}!!`;
  }
}

// クラス用の引数を定義する場合
export type HelloArgs = {
  readonly val: string;
}

呼び出し側

import { HelloWorld } from "./helloWorld"
// import * as hw from "./helloWorld"

const helloWorld: HelloWorld = new HelloWorld("Mike");
console.log(helloWorld.sayHello()); // Hello, Mike!!

【2】インターフェイス

interface IPerson {
  name: string
  sayHello(): string
}
interface IWorker {
  work(): void
}

// クラス定義
class Employee implements IWorker, IPerson {
  constructor(public name: string) {
    this.name = name
  }

  // メソッド
  sayHello(): string {
    return `Hello, ${this.name}!!`;
  }
  work() : void {
    console.log("Working...")
  }
}

// 呼び出し例
const employee: Employee = new Employee("Mike");
console.log(employee.sayHello()); // Hello, Mike!!

【3】トラブル「TypeError: Class extends value undefined is not a constructor or null」

以下「2)エラー発生時のソース」をコンパイルしたら
エラー「TypeError: Class extends value undefined is not a constructor or null」が発生した。
(詳細は、以下の「1)エラー内容」を参照)

1)エラー内容

TypeError: Class extends value undefined is not a constructor or null
  at Object.<anonymous> (C:\xxxx\sub-modules\deployment.ts:3:38)
  at Module._compile (node:internal/modules/cjs/loader:1103:14)
  at Module.m._compile (C:\xxxx\node_modules\ts-node\src\index.ts:439:23)
  at Module._extensions..js (node:internal/modules/cjs/loader:1155:10)
  at Object.require.extensions.<computed> [as .ts] (C:\xxxx\node_modules\ts-node\src\index.ts:XX:12)
  at Module.load (node:internal/modules/cjs/loader:981:32)
  at Function.Module._load (node:internal/modules/cjs/loader:822:12)
  at Module.require (node:internal/modules/cjs/loader:1005:19)
  at require (node:internal/modules/cjs/helpers:102:18)
  at Object.<anonymous> (C:\xxxx\index.ts:X:1)

2)エラー発生時のソース

index.ts (エラー発生時)

import { Deployment } from "./sub-modules/deployment";

export abstract class BaseDeployment {
  name: string
  constructor(name: string) {
    this.name = name
  }
  abstract deploy(): void;
}

const d = new Deployment()
d.deploy()

sub-modules/deployment.ts

import * as base from "../index";
//import { BaseDeployment } from "./baseDeployment";

export class Deployment extends base.BaseDeployment {
  constructor() {
    super("demo")
  }

  public deploy() {
    console.log(`test`)
  }
}

3)原因

https://qiita.com/taharah/items/ef69f2b722844cc249f6

より、どうも循環しているみたい

4)解決案

ファイルを分割した。

index.ts (修正版)

import { BaseDeployment } from "./sub-modules/baseDeployment";
import { Deployment } from "./sub-modules/deployment";

const d = new Deployment() as BaseDeployment
d.deploy()

./sub-modules/baseDeployment.ts (修正版・追加)

export abstract class BaseDeployment {
  name: string
  constructor(name: string) {
    this.name = name
  }
  abstract deploy(): void;
}

./sub-modules/deployment.ts (修正版)

import { BaseDeployment } from "./baseDeployment";

export class Deployment extends BaseDeployment {
  constructor() {
    super("demo")
  }

  public deploy() {
    console.log(`test`)
  }
}

参考文献

https://b1san-blog.com/post/ts/ts-class/

関連記事

TypeScript ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2020/12/21/180904
TypeScript ~ 基本編 ~
https://dk521123.hatenablog.com/entry/2021/02/15/000000
TypeScript ~ export / import ~
https://dk521123.hatenablog.com/entry/2022/03/23/233512
条件分岐
https://dk521123.hatenablog.com/entry/2022/03/12/000000
可変長引数
https://dk521123.hatenablog.com/entry/2016/02/08/111250
ループ操作 ~ map etc ~
https://dk521123.hatenablog.com/entry/2021/01/03/000000
配列・リスト操作
https://dk521123.hatenablog.com/entry/2021/02/10/225119
配列・リスト操作 ~ ソート編 ~
https://dk521123.hatenablog.com/entry/2021/02/24/222452
配列・リスト操作 ~ スプレッド構文 / Three-dots ~
https://dk521123.hatenablog.com/entry/2021/03/09/000000
Enum / Union / const assertion
https://dk521123.hatenablog.com/entry/2021/03/17/005906
TypeScript ~ 型エイリアス (Type Alias) ~
https://dk521123.hatenablog.com/entry/2022/04/02/000000
Visual Studio Code ~ TypeScript ~
https://dk521123.hatenablog.com/entry/2022/03/06/000000
JavaScript ~ クラス ~
https://dk521123.hatenablog.com/entry/2015/12/12/093900