Skip to content

Instantly share code, notes, and snippets.

@mtskf
Last active October 11, 2021 22:41
Show Gist options
  • Save mtskf/bafd7181889b50d1f6826f98a7b88c21 to your computer and use it in GitHub Desktop.
Save mtskf/bafd7181889b50d1f6826f98a7b88c21 to your computer and use it in GitHub Desktop.
[TypeScript] #TypeScript

クラスの基本的な書き方

class Person {
  fullName: string;

  constructor (public firstName: string, lastName: string, protected age: number) {
    // public/private/protected をつけると、オブジェクトのメンバーとして登録される。
    // 何も付けないとただのローカル変数となる。
    // protected は継承できる。 privateは継承できない。
    this.fullName = firstName + ' ' + lastName
  }

  greeting (this: Person) { // this: Student はなくてもいい。あると厳密に型チェックできる。
    console.log(`Hello! My name is ${this.fullName}. I'm ${this.age} years old.`);
  }

  incrementAge () {
    this.age++;
  }

  // static -> インスタンスを作らずにクラスを使う
  static species = 'Homo sapiens';
  static isAdult (age: number): boolean {
    return age > 17
  }
}

const me = new Person('Mitsuki', 'Fukunaga', 40);
console.log(me.firstName)  // 初期化に使った値もメンバーとしてアクセスできるよ。(publicの場合のみ)
console.log(me.lastName)   // lastNameはpublic付いてないからエラーになるよ。
me.incrementAge();
me.greeting()

// static を使う
console.log(Person.species);
console.log('Is 18 adult? ', Person.isAdult(18));

クラスの継承、Getter/Setter

// ⭐️ クラスの継承
class Student extends Person {
  constructor (firstName: string, lastName: string, age: number, private _major: string) {
    super(firstName, lastName, age);  // 継承したらsuperが必要。
  }
  greeting () {
    console.log(`Hello! My name is ${this.fullName}. I'm ${this.age} years old. My major is ${this._major}.`);
  }

  // ⭐️ Getter
  get major(): string {
    if (!this._major) {
      throw new Error('There is no major.');
    }
    return this._major;
  }

  // ⭐️ Setter
  set major(value: string) {
    if (!value) {
      throw new Error('There is no major.');
    }
    this._major = value;
  }
}

const student = new Student('John', 'Doe', 20, 'Math')
console.log(student.major)
student.major = 'Music'
student.greeting()

クラスとデコレータ

クラスが定義された時に関数を実行することができるよ。

function Logging (constructor: Function) {  // 引数にコンストラクタ関数を指定する
  console.log('Logging...');
  console.log(constructor);
}

@Logging  // クラス定義時に実行されるよ。
class User {
  name = "Mitsuki";
  constructor () {
    console.log('User was created!');
  }
}

const user1 = new User();

デコレータファクトリ

ファクトリ関数にすることで、デコレータに引数を渡すことができるよ

function Logging (message: string) {
  return function (constructor: Function) {
    console.log(`Logging ${message}...`);
    console.log(constructor);
  }
}

@Logging('User')
class User {
  name = "Mitsuki";
  constructor () {
    console.log('User was created!');
  }
}

const user1 = new User();

デコレータで簡易フレームワークを作る

function Component (template: string, selector: string) {
  return function (constructor: { new(...args: any[]): { name: string } }) {
    const mountedElement = document.querySelector(selector);
    const instance = new constructor();
    if (mountedElement) {
      mountedElement.innerHTML = template;
      mountedElement.querySelector('h1')!.textContent = instance.name
    }
  }
}

@Component('<h1>{{ name }}</h1>', '#app')
class User {
  name = "Mitsuki";
  constructor () {
    console.log('User was created!');
  }
}

Property decorator

function PropertyLogging (target: any, propertyKey: string) {
  console.log('propertyLogging');
  console.log(target);  // target はUserのProperty関数になる
  console.log(propertyKey);  // 'name'
}

class User {
  @PropertyLogging
  name = "Mitsuki";
  constructor () { }
}

Method decorator

function MethodLogging (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  console.log('methodLogging');
  console.log(target);       // Userのproperty
  console.log(propertyKey);  // 'greeting'
  console.log(descriptor);   // propertyDescriptor
}

class User {
  name = "Mitsuki";
  constructor () { }
  @MethodLogging
  greeting () {
    console.log('hello');
  }
}

Singleton

class Teacher {
  private static instance: Teacher;  // インスタンスはstaticで作る
  private constructor (public name: string) {  // private をコンストラクタに付ける
  }

  static getInstance () {
    // これで何度インスタンスを作っても同じものになるよ。
    if (!Teacher.instance) Teacher.instance = new Teacher('MitsukiF')
    return Teacher.instance;
  }

  greeting () {
    console.log(`Hello! My name is ${this.name}`)
  }
}

const teacher = Teacher.getInstance();
teacher.greeting();

関数でGenerics

型を引数として受け取る仕組み -> 関数を再利用するのに使う。

function copy<T>(value: T): T {   // <T>で型を引数Tとして受け取り、返り値の型を指定できる
  return value;
}
console.log(copy('hello')); // 型推論でstring型が返る
console.log(copy({ name: 'Mitsuki'})); // オブジェクト型が返る

クラスでGenerics

class LightDatbase<T extends string | number | boolean>{
  private data: T[] = [];
  add (item: T) {
    this.data.push(item);
  }
  remove (item: T) {
    this.data.splice(this.data.indexOf(item), 1);
  }
  get () {
    return this.data;
  }
}
const stringLightDatabase = new LightDatbase();
stringLightDatabase.add('Apple');
stringLightDatabase.add('Banana');
stringLightDatabase.add('Grape');
stringLightDatabase.remove('Banana');
console.log(stringLightDatabase.get());

interfaceでGenerics

interface TmpDatabase<T> {
  id: number;
  data: T[];
}
const tmpDatabase: TmpDatabase<number> = {
  id: 3,
  data: [32]
}

Type alias で Generics

type TmpDatabase<T> = {
  id: number;
  data: T[];
}
const tmpDatabase: TmpDatabase<number> = {
  id: 3,
  data: [32]
}

PromiseでGenerics

const fetchData: Promise<string> = new Promise(resolve => {
  setTimeout(() => {
    resolve('hello');
  }, 3000);
});

fetchData.then(data => {
  data.toUpperCase; // ここでstring型の補完が効くようになるよ。
});

Interfaceでオブジェクトの型を定義

Type alias でも同じことできるけど、Interfaceはオブジェクト専用だよ。

interface Person {
  firstName: string;
  lastName: string;
}

const greeter = (person: Person) => `Hello, ${person.firstName} ${person.lastName}!`
let user = { firstName: 'Mitsuki', lastName: 'Fukunaga' }
console.log(greeter(user))

ClassとInterface

implements を使うよ

interface Human {
  name: string;
  age: number;
  greeting (message: string): void;
}

class Developer implements Human {
  constructor (public name: string, public age: number) {
  }
  greeting (message: string) {
    console.log(`Hello ${this.name}, ${message}!`);
  }
}

const developer = new Developer('Mitsuki', 40);

Interfaceの継承

interface Namable {
  name: string;
  nickName?: string;  // 「?」はあってもなくても良いプロパティ。メソッドにもつけられるよ!
}

type Skills = 'js' | 'php' | 'ruby'

interface Human extends Namable {
  age: number;
  skill: Skills;
  greeting (message: string): void;
}

class Developer implements Human {
  constructor (public name: string, public age: number, public skill: Skills) {
  }
  greeting (message: string) {
    console.log(`Hello ${this.name}, ${message}!`);
  }
}

const developer = new Developer('Mitsuki', 40, 'js');

Interface のインターセクション

interface Engineer {
  name: string;
  role: string;
}

interface Blogger {
  name: string;
  follower: number;
}

interface EngineerBlogger extends Engineer, Blogger {}  // インターセクション型
const quill: EngineerBlogger = {
  name: 'Quill',
  role: 'front-end',
  follower: 1000
}

ちなみに、typeを使って書くとこうなる。

type Engineer = {
  name: string;
  role: string;
}

type Blogger = {
  name: string;
  follower: number;
}

type EngineerBlogger = Engineer & Blogger;  // インターセクション型
const quill: EngineerBlogger = {
  name: 'Quill',
  role: 'front-end',
  follower: 1000
}
{
"compilerOptions": {
....
},
"include": [ // コンパイルするファイルを明示的に指定
"src/**/*.ts"
],
"exclude": [ // 除外するファイルを明示的に指定
"**/*.spec.ts",
"node_modules"
]
}

複数の型を指定

let value: number | string | undefined;  // union型
let value: any;  // any型 - 何でも入れられるけど、なるべく避けよう
let value: unknown;  // unknown型も何でも入れられるけど、他の型に入れることはできないよ

オブジェクトの型

普通は型推論でいいけど、あえて書くならこうなる

const person: {
  name: string;
  age: number;
} = {
  name: 'Jack',
  age: 21
}	

配列の型

const fruits: string[] = ['Apple', 'Banana', 'Orange'];
const fruits: (string | number)[] = ['Apple', 'Banana', 3]  // 配列のUnion型
const fruits: any[] = ['Apple', 'Banana', 3, false]
const book: [string, number, boolean?] = ['business', 1500, false]  // Tuple型 - 厳密に個数や順番も指定できるよ

Enum(列挙型)

enum CoffeeSize {
  SHORT = 'Short',
  TALL = 'Tall',
  GRANDE = 'Grande',
  VENTI = 'Venti'
}
const coffee = {
  hot: true,
  size: CoffeeSize.SHORT
}
coffee.size = CoffeeSize.TALL   // こうすると、CoffeeSize型しか代入できないよ!|

// ちなみに、Enumは値をセットしないでもいけるよ。
enum CoffeeSize { SHORT, TALL, GRANDE, VENTI }
const coffee = {
  hot: true,
  size: CoffeeSize.SHORT
}

Literal型

Enumみたいに使えるよ!

let clothSize: 'small' | 'medium' | 'large'
clothSize = 'small'
clothSize = 'Medium'  // -> errorになるよ

オブジェクトの中でLiteral型を使える

const cloth: {
  color: string;
  size: 'small' | 'medium' | 'large';
} = {
  color: 'red',
  size: 'medium'
}

Type alias でスッキリ書くと…

type ClothSize = 'small' | 'medium' | 'large'
const cloth: {
  color: string;
  size: ClothSize;
} = {
  color: 'red',
  size: 'medium'
};

あるいは… オブジェクトのTypeAliasを使って、こう書いても良い

type Cloth = {
  color: string;
  size: 'small' | 'medium' | 'large';
}
const cloth: Cloth = {
  color: 'red',
  size: 'medium'
}

関数の型

const doubleNum = (n: number): number => n * 2
const doubleNum: (n: number) => number = n => n * 2  // どちらでも良い

// もしくは
type doubleNum = (n: number) => number;
const doubleNum: doubleNum = n => n * 2

// ちなみに、interfaceを使うとこう書ける
interface doubleNum {
  (n: number): number;
}
const doubleNum: doubleNum = n => n * 2

型アサーション

強制的に型を指定できる。 <>asのどちらでも良いけど、React/JSXを使う場合はタグと区別するため「as」を使うべし。

const input = <HTMLInputElement>document.getElementById('input');   // <>を使う場合
const input = document.getElementById('input') as HTMLInputElement; // asを使う場合
input.value = 'initial input value'   // ちゃんと型を指定しないと、.valueがエラーになるよ

Rest params の型を指定

function advancedFn (...args: readonly number[]) {  // readonlyを付けておいた方が安全だね!
}
advancedFn(0, 3, 3, 3, 3, 3);

// タプル型を組み合わせたりもできる
function advancedFn (...args: [number, string, boolean, ...number[]]) {
}
advancedFn(0, 'hello', true, 3, 3, 3, 3, 3);

windowにメンバーを追加

// @ts-ignore
(window as any).testData = testData;

Optional Chaining

console.log(downloadedData.user?.name?.first);  // user もしくは user.name がなかったら undefined を返す

Nullish Coalescing

const userData = user.name ?? 'no-name'  // user.nameがnull or undefinedのとき、デフォルト値を代入
const userData = user.name || 'no-name'  // user.nameが空文字 or falseでも、置換されてしまう。

const assertion

const milk = 'milk' as const;
const array = [10, 20] as const;  // 配列の中身も変更できなくなるよ
const peter = {
  name: 'Peter',
  age: 38
} as const;  // オブジェクトの中身も変更できなくなる
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment