LoginSignup
3
4

More than 1 year has passed since last update.

TypeScript②(型の機能)

Last updated at Posted at 2021-06-23

この記事について

この記事は私がTypeScriptについて学んだ内容を
TypeScript①(基礎)
TypeScript②(型の機能)
TypeScript③(ユーティリティタイプ)
の3回に分けて網羅的に紹介していきます。

①(基礎)では導入方法と様々な型について簡単な紹介
③(ユーティリティタイプ)では型変換を容易にするユーティリティ型について

今回は型が実際にどういう働きをするのかについてです。

参考リンク

型安全

TypeScriptには強力な型推論が備わっており、必ずしも型を定義しないといけないわけではありません。
ですが、場合によって明示的に型を指定して型安全を担保することが必要です。

nullチェック

次の関数では引数がnullの場合でもコンパイルを通してしまい、エラーの発見が遅れる可能性があります。

function foo(num) {
  return `${num.toFixed(1)}`;
}
console.log(foo(0.1003)); // 0.1
console.log(foo(null)); // ランタイムエラー(コンパイルは通る)

このようなnumberとnullのどちらの値もありえるという状況はnumber | nullという型で表すことができます。
number | null型の値はnullかもしれないので、numberとして扱ったりプロパティを参照したりすることができません。

function foo(num: numbar | null) {
  return `${num.toFixed(1)}`;
}
console.log(foo(0.1003)); // 0.1
console.log(foo(null)); // コンパイルエラー

これに対し、nullでなければ処理したいという場面はif文でnullチェックを行う方法があり、TypeScriptはこれを適切に解釈して型を絞り込んでくれます。

function foo(num: number | null) {
  if (num != null) { // この行でのnullの型推論 num: number | null
    return `${num.toFixed(1)}`; // この行でのnullの型推論 num: number
  } else {
    return 0;
  }
}
console.log(foo(0.1003)); // 0.1
console.log(foo(null)); // 0

引数をオプションにする

引数に?をつけることで引数のない場合を許容する事ができます。
しかし以下の場合にはエラーは発生しませんがundefinedが返されます。

function greet(name?) {
  console.log(`Hello ${name}`);
}
greet("Mako"); // Hello Mako
greet(); // Hello undefined

上記のようにnameの型は自動的にstring | undefinedになるため、undefinedチェックを行う必要があります。

function greet(name?) {
  return name != null ? console.log(`Hello ${name}`) : console.log("Hell0");
}
greet("Mako"); // Hello Mako
greet(); // Hello

readonly

プロパティにreadonlyをつける事でプロパティへの再代入を防ぐ事できます。

type Foo = {
  readonly name: string;
};
const foo: Foo = {
  name: "Ken",
};
foo.name = "Reo";
console.log(foo); // コンパイルエラー

アノテーション・アサーション

アノテーションは型の注釈のこと

num: number;

アサーションは下記の右辺の型の宣言のこと

let num: number = (<string>comment).length;
let num: number = (comment as string).length;

ダウンキャスト

タイプアサーションを使用して、より具体的なタイプを指定するのがダウンキャストです。

const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;

アップキャスト

ダウンキャストの逆に型の抽象度を上げるのがアップキャストです。

function foo(num: string) {
  return console.log((num as any) * 2);
}
foo("3"); // 6

インデックスシグネチャ

下記の[k: string]をンデックスシグネチャといい、定義されていないプロパティを追加する事ができます。

type Foo = {
    [k: string]: any; 
};
const foo: Foo = {
  name: "Nana",
  age: 20,
};

またインデックスシグネチャには様々な制限をかけるができます。

type Fruit = "apple" | "banana" | "lemon";
type Foo = {
  [k: string]: Function | Promise<any> | Fruit;
};

const

constで適用される型はWidening Literal Typesという型で、下記のような違いと利用方法があります。

let foo = "foo"; // stringとして推論される
const bar = "bar"; // "bat"と推論される

const Baz = {
    a: "a", // a: "a"
    b: "b", // b: "b"
    c: "c". // c: "c"  
} as const

Typeof

typeof演算子は与えられた値の型を文字列で返す演算子です。これを利用して安全に処理することができます。

function foo(value: string | number) {
  if (typeof value === "string") {
    return console.log(value.length);
  }
  return console.log(value * 2);
}
foo("100"); // 3
foo(100); // 200

in

in演算子は与えられた値が型に含まれているかの評価結果をbooleanで返します。in演算子を使うことで型の絞り込みが可能です。

type Foo = {
  color: string;
  point: number;
};
type Bar = {
  name: string;
  age: number;
};
function log(obj: Foo | Bar) {
  if ("color" in obj) { // obj: Foo | Bar
    return console.log(obj); // obj: Foo
  }
  if ("age" in obj) { // obj: Bar
    return console.log(obj); // obj: Bar
  }
}

このほかにinstanceof演算子を利用した絞り込み方法などもあります。

typeとinterface

型を定義するためにtype、interfaceの二つのの方法がありますが、この二つにはプロパティをオーバーロードできるかどうかという違いがあります。

interface User {
  name: string;
}
interface User {
  age: number;
}
// Userを利用する際このように結合されます
interface User {
  name: string;
  age: number;
}
type User = { // User重複によるエラー
    name: string
}
type User = { // User重複によるエラー
    age: number
}

Generics

型を変数にすることで様々なコンポーネントで再利用できるようにする事ができます。

interface Foo<T> {
  foo: T;
}
const bar: Foo<string> = {
  foo: "foo",
};

型の初期値を指定

interface Foo<T = string> {
  foo: T;
}

extendsによる型の制約

function foo<T extends string>(value: T) {
  console.log(value);
}
foo("Hello"); // Hello

keyof と T[K]

keyof TTの型が付与され、T[K]Tの型が持つKの型が付与される。
これによりキャストの省略が可能になる。

interface Foo {
  bar: string;
  baz: number;
}
type Hoge = keyof Foo;  // string | numbar
type Fuga = Foo["bar"]; // string
type Peyo = Foo["baz"]; // number

keyofとT[K]を組み合わせた利用。

function hoge<T, K extends keyof T>(user: T, key: K): T[K] {
  return user[key];
}
interface User {
  name: string;
  age: number;
}
const user = {
  name: "Takeru",
  age: 25,
};
const foo = hoge(user, "name"); // const foo: string
const bar = hoge(user, "age"); // const bar: number

Mapped Types

Mapped Typesは{[P in K]: T}という構文を持つ型です。
これが何をするかというと雑にいえば型世界でのmapメソッドです。

{[P in K]: T} // Pの中身をK、その型をTにする

type Item = { a: string; b: number; c: boolean; };
type Foo = {[P in keyof Item]: string}
// Pの中身を{a, b, c}にして、その型をstringにする{a: strting, b: string, c: string}
// それをFooに代入している
// type Foo = {a: strting, b: string, c: string}

Conditional Types

Conditional TypesはT extends U ? X : Yという構文を持つ型です。
三項演算子を利用して条件分岐ができます。

type Foo<T> = T extends string ? true : false; // string型をture型に変換し、それ以外の型をfalse型に変換します。
type Bar = Foo<"foo">; // type Bar = true
type Baz = Foo<0>; // type Baz = false

Mapped Types × Conditional Types

{[P in K]: T}T extends U ? X : Yを組み合わせた例です。

type Item = { a: string; b: number; c: boolean };
type Foo<T, U> = {
  [K in keyof T]: T[K] extends U ? true : false;
};
type Bar = Foo<Item, string>; // type Bar = {a: true; b: false; c: false; };
type Baz = Foo<Item, number>; // type Baz = { a: false; b: true; c: false; };

今回は型で型を変換するという複雑な内容でしたが、次のTypeScript③(ユーティリティタイプ)ではスクラッチで型変換ロジックを作成なくても、TypeScriptが元々持っている型変換を容易にできる型(ユーティリティタイプ)のついて紹介します。

関連記事

TypeScript①(基礎)
TypeScript③(ユーティリティタイプ)

3
4
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
3
4