1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

TypeScript 勉強まとめ

1
Posted at

最近 TypeScript の勉強をしているので、学んだことメモ

TypeScript とは

  • JavaScript を拡張した言語
  • 最終的に JavaScript にコンパイルして使用される
  • 型を持つため、潜在的なエラーを把握することができる
  • JavaScript にない新しい機能を含む
    • インターフェース
    • ジェネリクス
    • デコレータ

インストール

  • node.js をインストール

    • node.js の公式サイトからインストーラを入手し、インストール
  • npm で TypeScript をインストール

npm install -g typescript

コンパイラ

コンパイルする ts ファイルを指定して、tsc コマンドを実行する

tsc app.ts

ウォッチモード

ts ファイルを変更すると、自動でコンパイルが行われるようになる

tsc app.ts --w

プロジェクト全体のコンパイル

まず、プロジェクトフォルダであることを指定する

cd [プロジェクトのルートフォルダ]
tsc --init

tsconfig.json ファイルが作成される
tsc コマンドを実行すると、プロジェクト内のすべての ts ファイルがコンパイルされる

tsc

コンパイル する/しない ファイルを指定する

tsconfig.json に include/exclude を指定し、コンパイラの対処に する/しない ファイルを指定する

  • include
    • コンパイルするファイル
    • 指定しなければ、すべての ts ファイルをコンパイルする
  • exclude
    • コンパイルしないファイル
    • フォルダを指定することも可能
    • node_modules フォルダは、デフォルトでコンパイルしないようになっている

型定義

const number1 = 5;    // この場合、number 型であることが明確なので型指定は不要(推論)

let number2: number;  // この場合、型がわからいないので要指定
number2 = 'aa';       // エラーになる

let number3;          // 型を指定しないと、any 型になる
number3 = 'bb';       // any 型なのでエラーにならない

引数

function add(num1: number, num2: number, isShow: boolean) {
  const result = num1 + num2;
  if (isShow) {
    console.log(result);
  }
  return result;
}

Object

const book = {
  title: "aaa",
  page: 200,
}

上記のようなオブジェクトの場合、下記のように型が定義される

{
  title: string;
  page: number;
}

明示的に型を指定する場合は以下のように書く(ただし、推論が可能な場合は推奨されない)

const book: {
  title: string;
  page: number;
} = {
  title: 'aaa',
  page: 200,
}

Array

let bookshelf: string[];  // string 型の Array と指定
bookshelf= ["aaa", 1];    // string じゃないので「1」のところでエラー

Tuple : 長さが指定された配列。JavaScript にはない

let book: [number, string] = [2, "author"];

book[1] = 10;             // string じゃないのでエラー
book = [0, 'bbb', 'ccc']; // 長さが一致しないのでエラー

Enum : 列挙型。JavaScript にはない

enum Genre {
  BUSINESS,
  COMIC,
  NOVEL,
}

const book = {
  // ~
  genre: Genre.COMIC,
};

Any : 型をチェックしない。JavaScript にはない

let bookshelf: any[];   // 配列であれば、中身はどんな型でもOK
bookshelf= ["aaa", 1];  // エラーにならない

any は極力使わないようにすべき

Union : 柔軟な型定義

// input1, input2 は number型か string型を受け取れる
function add(input1: number | string, input2: number | string) {
    // ~
}

Literal : 値そのものを定義する

// resultType には、"as-number" か "as-text" のみ指定可能
function add(input1: number, input2: number, resultType : "as-number" | "as-text") {
  // ~
}

エイリアス(type) : Union 型や Literal 型を別の名前で定義する

type InputType = number | string;
type ResultDescriptor = "as-number" | "as-text";

function add(input1: InputType, input2: InputType, resultType : ResultDescriptor) {
    // ~
}

function、void

// return の型を指定する場合(推論される場合は記述不要)
function add(n1: number, n2: number): number {
  return n1 + n2;
}

// return がない場合 void 型になる
function printNum(num: number): void {
  console.log("Result: " + num);
}

// function の型を指定(2つの number の引数、number の戻り値)
let addValues: (a: number, b: number) => number;
addValues = add;       // OK
addValues = printNum;  // 関数の引数、戻り値の型が異なるのでエラー

unknown : どの型が入るかわからない

let unknownInput: unknown;
let strInput: string;

unknownInput= 5;
unknownInput= "aaa";

strInput= unknownInput;  // unknownInput が string と保証されないのでエラー

// unknown を代入する場合は、型チェックが必要
if (typeof unknownInput=== "string") { 
  strInput= unknownInput;
}

unknownInput を any型 とするとエラーにならない(any は型チェックをしないため)

never : 何も返さない関数の戻り値

function generateError(message: string, code: number): never {
  throw { message: message, errorCode: code };
}

generateError("エラーが発生しました", 500);

戻り値がない場合 void でもいいが、戻り値は絶対にありえないということを明示的に示す場合に never を使う

型キャスト

TypeScript が型を取得できない場合に、開発者が明示的に指定する方法

  • 方法1 : <型名> 式
  • 方法2 : 式 as 型名
// 下式のどちらか
const element1 = <HTMLInputElement>(document.getElementById("id1")!);
const element2 = document.getElementById("id2")! as HTMLInputElement;

インターフェース

  • オブジェクトがどんな形かを定義するもの
  • 具体的な値を設定することはできない
  • オブジェクトの型として使用でき、構造が合っているかチェックできる
interface MsgInterface {
  name: string;
  showMessage(phrase: string): void;
}

// interface の実装クラス
class Message implements MsgInterface {
  constructor(public name: string) {}

  showMessage(phrase: string) {
    console.log(phrase + " " + this.name);
  }
}

const msg1 = new Message("World");
msg1.showMessage("Hello");

インターフェースは継承も可能

interface Named {
  readonly name: string;
}

interface MsgInterface extends Named {
  showMessage(phrase: string): void;
}

関数の型の定義

interface AddInterface {
  (a: number, b: number): number;
}

let add: AddInterface;

add = (n1: number, n2: number) => {
  return n1 + n2;
};

任意のプロパティ

必須でないプロパティには「?」を付けることでオプションにできる

interface Named {
  readonly name: string;
  outputName?: string;  // outputName はオプション
}

ジェネリクス

ジェネリクス関数

関数の定義時には引数を明確にしておらず、関数の使用時に引数を明確する

// Generic 関数
function merge<T, U>(objA: T, objB: U) {
  return Object.assign(objA, objB);
}

const mergedObj = merge({ name: "aaa" }, { age: 30 });

ジェネリック型に制約を付ける

T extends *** で型に制限を付ける

// Generic 関数(引数をobjectで制限)
function merge<T extends object, U extends object>(objA: T, objB: U) {
  return Object.assign(objA, objB);
}

const mergedObj = merge({ name: "aaa", hobbies: ["sports"] }, { age: 30 });

keyof 制約

引数の一つが、別のオブジェクト型引数のキーになっていることを制約する

function getValue<T extends object, U extends keyof T>(
  obj: T,
  key: U
) {
  return "Value: " + obj[key];
}

getValue({ name: "Max" }, "name");

ジェネリック型のユーティリティ

  • Partial

    • ジェネリックを一時的にオプションにする
  • readonly

    • オブジェクトのプロパティの追加や変更、配列の push や pop が使えない

ジェネリック型と Union 型の使い分け

  • Generic
    • クラス、関数の最初に型を一つに制限することができる
  • Union
    • Union に指定されている、いずれかの型を常に受け入れることができる

デコレータ

事前準備

  • tsconfig.json で "experimentalDecorators": true のコメントを外す

基本的な Decorator

  • 関数をクラスに適用させる

  • クラスを定義した時に実行される(インスタンス化したときではない) → クラスの初期設定に関わる処理に使える

function Logger(constructor: Function) {
  console.log("ログ出力中...");
  console.log(constructor);
}

@Logger
class Person {
  name = "aaa";

  constructor() {
    console.log("Person オブジェクトを作成中...");
  }
}

Decorator ファクトリー

Decorator 関数に引数を渡す

function Logger(logString: string) {
  return function (constructor: Function) {
    console.log(logString);
    console.log(constructor);
  };
}

@Logger("ログ出力中...")
class Person {
  name = "aaa";

  constructor() {
    console.log("Person オブジェクトを作成中...");
  }
}

1つのクラスに複数ので Decorator を付けることが可能

  • Decorator の作成は上から順に実行
  • Decorator は下から順に実行
function Logger(logString: string) {
  console.log("Loggerファクトリー");
  return function (constructor: Function) {
    console.log(logString);
    console.log(constructor);
  };
}

function WithTemplate(template: string, hookId: string) {
  console.log("Templateファクトリー");
  return function (_: Function) {
    console.log("テンプレートを表示");
    const hookEl = document.getElementById(hookId);
    if (hookEl) {
      hookEl.innerHTML = template;
    }
  };
}

@Logger("ログ出力中")
@WithTemplate("<h1>Person オブジェクト<h1>", "app")
class Person {
  name = "Max";

  constructor() {
    console.log("Person オブジェクトを作成中...");
  }
}

// --- ブラウザの Console の出力 ---
// Loggerファクトリー
// Templateファクトリー
// テンプレートを表示
// ログ出力中

Decorator を配置できる場所

  • クラス
  • プロパティ
  • メソッド
  • パラメータ
function Log(target: any, propertyName: string | Symbol) {
  console.log("Property Decorator");
  console.log(target, propertyName);
}

function Log2(target: any, name: string, descripter: PropertyDescriptor) {
  console.log("Accessor Decorator");
  console.log(target);
  console.log(name);
  console.log(descripter);
}

function Log3(
  target: any,
  name: string | Symbol,
  descripter: PropertyDescriptor
) {
  console.log("Mehotd Decorator");
  console.log(target);
  console.log(name);
  console.log(descripter);
}

function Log4(target: any, name: string | Symbol, position: number) {
  console.log("Parameter Decorator");
  console.log(target);
  console.log(name);
  console.log(position);
}

class Product {
  @Log
  title: string;
  private _price: number;

  @Log2
  set price(val: number) {
    if (val > 0) {
      this._price = val;
    } else {
      throw new Error("不正な価格です - 0 以下は設定できません");
    }
  }

  constructor(t: string, p: number) {
    this.title = t;
    this._price = p;
  }

  @Log3
  getPriceWithTax(@Log4 tax: number) {
    return this._price * (1 + tax);
  }
}
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?