もがき系プログラマの日常

もがき系エンジニアの勉強したこと、日常のこと、気になっている技術、備忘録などを紹介するブログです。

TypeScript イチから練習

はじめに

こんばんは。 TypeScriptいままでなんとなく他の人のコード見て使ってて、ちゃんとイチから勉強してなかったので、昔買ってたこちらの本見て、ちょっと勉強してみました。

環境構築&初期設定

プロジェクトの作成

$ npm init --yes
{
  "name": "example02",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

TypeScriptのインストール

$ npm install --save-dev typescript

added 1 package, and audited 2 packages in 1s

found 0 vulnerabilities

TypeScriptのバージョン確認

$ npx tsc --version
Version 4.4.4

tsconfig.jsonの作成

$ npx tsc --init
message TS6071: Successfully created a tsconfig.json file.

tsconfig.jsonの設定

{
  "compilerOptions": {
    "target": "es5",                          // コンパイル後のECMAScriptのバージョン
    "outDir": "dist",                         // コンパイル後のファイルの設置場所
    "rootDir": "src",                         // Typescriptのディレクトリ起点
    "module": "commonjs",                     // 出力されるJavaScriptがどのようにモジュールを読み込むか
    "esModuleInterop": true,                  // commonJS形式で書かれたモジュールをインポートできるようなる
    "allowJs": true,                          // jsとtsの共存を許可する
    "checkJs": true,                          // jsdocで定義した型の形検査を行う
    "sourceMap": true,                        // ソースマップの出力有無
    "skipLibCheck": true,                     // *.d.tsファイルに対する型チェックをスキップする
    "forceConsistentCasingInFileNames": true, // importのファイルパスん大文字小文字を区別する
    "strict": true                            // 厳格チェック    
  },
  // コンパイル対象にするディレクトリ位置
  "include": [
    "src/**/*" 
  ],
  // コンパイル対象外とするファイル群
  "exclude": [
    "node_modules",
    "**/*.test.ts"
  ]
}

型の初歩

プリミティブ型

const bool: boolean = true;
const number1: number = 10;
const string1: string = "Kojirock";

// or null
const bool2: boolean | null = false;
const bool3: boolean | null = null;

リテラル

let str: 'Hello';
str = 'Hello'
str = 'aaaa'; => エラー 型 '"aaaa"' を型 '"Hello"' に割り当てることはできません。

# 文字リテラル型とUnion型
type Direction = 'top' | 'right' | 'bottom' | 'left';
function move(direction: Direction) {
  switch (direction) {
    case 'top':
      return '上';
    case 'right':
      return '右';
    case 'bottom':
      return '下';
    case 'left':
      return '左';
  }
}

配列型

const arr1: number[] = [1, 2, 3];
const arr2: Array<number> = [1, 2, 3];

オブジェクト型

// オブジェクト型
function getAge(user: { age: number }): number {
  return user.age;
}
const user1 = { age: 37 };
const user2 = { age: 30, name: 'AAA' };
getAge(user1); => 37
getAge(user2); => 30

Interface

interface User {
  name: string
}
function getNmae(user: User): string {
  return user.name;
}
const user: User = {
  name: 'AAA'
};
getNmae(user); => 'AAA'

Interfaceの継承

interface Point {
  x: number;
  y: number;
}
interface Point3D extends Point {
  z: number;
}
const point: Point3D = {
  x: 100,
  y: 200,
  z: 150,
}

オプションプロパティ

undefinedを許可する

interface UserProfile {
  name: string;
  nameKana?: string;
}
const profile: UserProfile = {
  name: 'kojirock',
}

インデックスシグネチャ

動的なプロパティを型にする

interface StringDirectory {
  [key: string]: string;
}
const dic: StringDirectory = {};
dic['key1'] = 'value1';
dic['key2'] = 'value2';

列挙型

enum Color {
  RED = 'red',
  BLUE = 'blue',
  YELLOW = 'yellow',
}
enum LogLevel {
  VERBOSE,   // 0
  INFO = 10, // 10
  WARNING,   // 11
  ERROR,     // 12
}

関数型

// (x: number,y: number,) => numberまでが定義
// 引数と戻り値の型を指定している
const add: (x: number,y: number,) => number = function (x: number, y: number): number {
  return x + y;
}
add(100, 200); => 300

クラス

class Point {
  private x: number;
  private y: number;

  public constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }

  public offset(dx: number, dy: number): void {
    this.x += dx;
    this.y += dy;
  }

  public getX(): number {
    return this.x;
  }

  public getY(): number {
    return this.y;
  }
}
const point = new Point(10, 20);
point.offset(40, 40);
point.getX(); // => 50
point.getY(); // => 60

Union型

どちらかの型に該当すること anyみたいな使い方にならないように注意

function stringify(value: string | string[]): string {
  if (Array.isArray(value)) {
    return value.join(',');
  }
  return value;
}
stringify('Hello');
stringify(['Hello', 'World']);

intersection型

どちらの型にも該当すること

interface View {
  x: number;
  y: number;
}
interface Rectangle {
  width: number;
  height: number;
}
const rectangleView: View & Rectangle = {
  x: 500,
  y: 600,
  width: 24,
  height: 24
};

エイリアス

オブジェクト型の型エイリアスはinterfaceと似ている

interfaceの場合、同名で2回定義するとマージされてしまう

エイリアスの場合はコンパイルエラーが起こる

type UserId = number;
type UserName = string;
type Plan = 'Free' | 'Pro' | 'ADVANCE';
type UserProfile = {
  userId: UserId
  name: UserName
  plan: Plan
}

ジェネリック

具体的な型ではなく、「抽象的な」型を扱うことが出来る

本ではあまり理解できなかったけど、こちらのサイトの説明がとてもわかり易かった

testの引数はstring型だけ、またtestの引数はnumber型だけが許されるようになります。

抽象的な型引数を関数に与え、実際に利用されるまで型が確定しない関数を作成しています。

function test<T>(arg: T): T {
  return arg;
}
test<number>(1); //=> 1
test<string>("文字列"); //=> 文字列

アサーション

// 型アサーション
const value1: any = 'kojirock';
const value2: number = (value1 as string).length

Utility Types

本では一部のみの紹介だったので、 こちら のサイトを参考にさせてもらった

1. すぐに使い道が思いついたもの

Partial<T>

すべてのプロパティをオプショナル(オプションプロパティ)にする

要するに全部 ? がつくようになる

type UserId = number;
type UserName = string;
type Plan = 'Free' | 'Pro' | 'ADVANCE';
type UserProfile = {
  userId: UserId
  name: UserName
  plan: Plan
}

const kojrock: Partial<UserProfile> = {
  name: 'kojirock'
}

f:id:kojirooooocks:20211030145839p:plain

Required<T>

type UserId = number;
type UserName = string;
type Plan = 'Free' | 'Pro' | 'ADVANCE';
type UserProfile = {
  userId: UserId
  name: UserName
  plan: Plan
}

const kojrock: Required<UserProfile> = {
  name: 'kojirock',
}

Partialの逆で、全部を必須プロパティとなる

f:id:kojirooooocks:20211030145852p:plain

Readonly<T>

すべてのプロパティを readonlyにしてくれる

type UserId = number;
type UserName = string;
type Plan = 'Free' | 'Pro' | 'ADVANCE';
type UserProfile = {
  userId: UserId
  name: UserName
  plan: Plan
}

const kojrock: Readonly<UserProfile> = {
  name: 'kojirock',
  userId: 100,
  plan: 'Free'
}
kojrock.name = 'bbb'

f:id:kojirooooocks:20211030145904p:plain

Pick<T,K>

T型の中からKで選択したプロパティのみを含んだものを構築して返す

type UserId = number;
type UserName = string;
type Plan = 'Free' | 'Pro' | 'ADVANCE';
type UserProfile = {
  userId: UserId
  name: UserName
  plan: Plan
}
type KojirockProfile = Pick<UserProfile, 'userId' | 'name'>

const kojirock: KojirockProfile = {
  userId: 200,
  name: 'BBB',
}
const kojirock2: KojirockProfile = {
  name: 'BBB',
}

f:id:kojirooooocks:20211030145914p:plain

Omit<T,K>

T型の中からKで選択したプロパティを除いたものを構築して返す

type UserId = number;
type UserName = string;
type Plan = 'Free' | 'Pro' | 'ADVANCE';
type UserProfile = {
  userId: UserId
  name: UserName
  plan: Plan
}
type KojirockProfile = Omit<UserProfile, 'userId' | 'name'>

const kojirock: KojirockProfile = {
  plan: 'Free',
}
const kojirock2: KojirockProfile = {
  plan: 'Free',
  name: 'BBB',
}

f:id:kojirooooocks:20211030145925p:plain

NonNullable<T>

T型からnullとundefinedを取り除いた型を構築して返す

type UserId = NonNullable<number|null>;
type UserName = string;
type Plan = 'Free' | 'Pro' | 'ADVANCE';
type UserProfile = {
  userId: UserId
  name: UserName
  plan: Plan
}

const kojirock: UserProfile = {
  userId: null,
  name: 'BBB',
  plan: 'Free'
}

f:id:kojirooooocks:20211030145938p:plain

2. 使い方はわかったけど、使いみちがイメージできてないもの

Record<K, T>
Exclude<T,U>
Extract<T,U>
Parameters<T>
ConstructorParameters<T>
ReturnType<T>
ThisParameterType
OmitThisParameter
ThisType<T>

終わりに

本では treeコマンドを作るよう流れになってます。

自分も本に沿って作ってみました。

今までなんとなーく他の人のコード見てコピって使ってとかやってて、ジェネリクスとかはぶっちゃけわかってなかったので、おさらいしてよかったです。

まだまだ全然ですが、なんとなく感触をつかめました。

Utility Typesで使い道がわからない所、もうちょい勉強したいと思いました。

あとはガッツリTSでなにか作ってみたいな。

現場からは以上です。