今アツいプログラミング言語!TypeScriptに迫る!

f:id:f-a24:20210114174739p:plain

こんにちは。フロントエンドエンジニアの藤澤(@f_a24_)です。
本稿では、今とても勢いのあるプログラミング言語TypeScriptについて紹介したいと思います。

TypeScriptとは?

f:id:f-a24:20200528175728p:plain

TypeScriptMicrosoft製のAltJS(JavaScriptに変換される言語)で、一言で言うと型のあるJavaScriptです。

Googleの社内標準言語になっていたり、直近1年で最も使われているプログラミング言語第4位になっていたりと 特にWebフロントエンド界隈を中心にデファクトスタンダード化しており、多くのプロダクトやライブラリにTypeScriptが使用されています。

直近1年で最も使われているプログラミング言語ランキングf:id:f-a24:20210118112314p:plain

自社のWeb制作サービスBiNDupでも、サイトシアターやSiGN、SmoothGrow、Dress、SHiFTといった多くの機能に採用しています。

TypeScriptの魅力

型があるJavaScriptということで、覚えるのが大変そう!や面倒くさい!と誤解されたり身構えられることがありますが、型推論によってあまり型に縛られ過ぎず、かつ型があることでの恩恵を受けられます。
具体的には、以下のような部分がTypeScriptを使うことによって受けられる恩恵になります。

  • コードを書いてる時点で型によるエラーに気づけるので開発スピードを高め、コードの品質も上がる
  • 型があることでドキュメント代わりになり、コードの読みやすさを高める(保守性が上がる)
  • エディタによる補完が強力になり、開発体験が向上する

また、TypeScriptの型は厳格さと柔軟さをほどよく兼ね備えているので開発者のレベルを問わず導入できるのも大きな魅力の1つです。

始め方

オンラインエディタ

下記のようなオンラインエディタを使用すると手っ取り早く使うことができます。

  • typescriptlang → 公式
  • codesandbox → 「Create Sandbox」で「Vanilla Typescript」を選択する
  • codepen → 「Pen Settings」のJSから「JavaScript Preprocessor」で「TypeScript」を選択する

ローカル環境

Node.jsがインストールされていれば、グローバル環境に追加するだけで使用することができます。

  1. npm i -g typescriptコマンドでTypeScriptをインストール
  2. index.tsを作成する
  3. tsc index.tsコマンドでTypeScriptファイルをコンパイル
  4. コンパイルされたindex.jsが作成される

実際のプロダクトなどでは、フロントエンドでwebpackなどのバンドルツールやReactなどのライブラリと一緒に使用したり、サーバーサイドはExpressなどのフレームワークと一緒に使う、もしくはNestJSなどのTypeScript製フレームワークを使用するのが一般的だと思われます。

基本

構文自体はJavaScriptとほぼ同じなので、すぐに覚えられるかと思います。

// 変数や関数、引数などの後ろに「: 型名」を指定することで型を宣言
let name: string = 'John';
function hello(name: string): void {
  console.log(`Hello ${name}!`);
}
hello(your_name);

// 型が分かっている場合は型推論される
const hello = 'hello'; // constの場合は'hello'型に推論される
let world = 'world'; // string型に推論される
function helloWorld() { // 戻り値がstring型に推論される
  return 'Hello World';
}

指定できる基本的な型は下記になります。

// Boolean:真偽値型
let isDone: boolean = false;

// Number:数値型
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;

// String:文字列型
let color: string = "blue";

// Array:配列型
// 後述のGenerics(Array<T>)を使った形も可能
let numberList: number[] = [1, 2, 3];
let stringList: Array<string> = ['a', 'b', 'c'];

// Tuple:タプル型
// 配列によく似ているが、それぞれの位置の要素の型を明示しておくことができる
let x: [string, number];
x = ["hello", 10];
x = [10, "hello"]; // 型の位置が違うのでエラー

// Enum:列挙型
enum Color {Red, Green, Blue}
let c: Color = Color.Green;
/* Color: { 0: "Red", 1: "Green", 2: "Blue",
 Blue: 2, Green: 1, Red: 0 }
 c: 1
 */

// Unknown:型安全に任意の値を代入できる型
// 型を特定するまであらゆる操作が制限される
let notSure: unknown = 4;
notSure = "maybe a string instead";
if (typeof notSure === "string") {
  const aString: string = notSure;
  const aBoolean: boolean = notSure; // 文字列型なのでエラー
}

// Any:任意の値を代入できる型
// TypeScriptを使っている意味が薄れてしまう
let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false;

// Void:値がないことを意味する型
// 関数の戻り値の型注釈に使用
function warnUser(): void {
  console.log("This is my warning message");
}
let unusable: void = undefined;

// NullとUndefined
let u: undefined = undefined;
let n: null = null;

// Never:発生し得ない値の型
function error(message: string): never {
  throw new Error(message);
}

// Object:プリミティブ型(number、string、booleanなど)以外の型
function create(o: object) {
  console.log("This is my warning message");
}
create({ prop: 0 });
create(42); // プリミティブ型なのでエラー

高度な型

基本的な型以外にもいくつかの機能を取り上げて紹介します。

// Interface:オブジェクト型
// 基本的な型のobjectより具体的な型(プロパティ名とその型)が定義できる
// オブジェクトや関数、クラスの仕様を定めるためのもの
interface MyObj {
  hoge: string;
  foo: number;
}

// Type Alias:型に対して名前をつける
// 複数の場所で再利用しようと思っている型に対して名前をつけるためのもの
type Apple = 'Apple';
type ObjType = {
  hoge: string,
  apple: Apple
};

// InterfaceとType Aliasの違い
// Interfaceは同名で定義することで拡張可能
// Type Aliasを拡張するには後述の交差型を使用
interface Bar {
  hoge: 'hoge';
}
interface Bar {
  foo: 1;
}
const bar: Bar = {
  hoge: 'hoge',
  foo: 1
};

// Intersection Types:交差型
// 複数の型を1つに結合する
type Hoge = {
  hoge: string,
  foo: number
};
type Foo = {
  foo: number,
  bar: boolean
};
const bar: Hoge & Foo = {
  hoge: 'hoge',
  foo: 1,
  bar: true
};

// Union Types:合併型
// 複数の型のうち1つの型が成立することを示す
let value: number | string;
value = 1;
value = '2';

// Literal Types:リテラル型
// より正確な値を指定できる
let hoge: 'hoge' = 'hoge';
hoge = 'foo'; // 'hoge'型なのでエラー

// 型アサーション
// 任意の方法で型を上書きする
let foo: string | number;
function fn(bar: string) {
  console.log(bar);
}
fn(foo); // number型もありえるのでエラー
fn(foo as string);

// Generics:総称型
// 型の決定を遅延でき、型における変数のような機能
type Hoge<T> = {
    a: T,
    b: number
};
const hoge: Hoge<string> = {
  a: 'hoge',
  b: 1
};
hoge.a = 0; // string型なのでエラー

// Conditional Types:型定義における条件分岐
type Diff<T, U> = T extends U ? never : T;
type Go = Diff<'red' | 'yello' | 'blue', 'red'>; // 'yello | blue'

// 引数の型によって値が決まるような関数も定義することができる
function isNumber<T>(arg: T): T extends number ? number : 'not number' {
  const result: any = (typeof arg === 'number') ? arg * 10 : 'not number';
  return result;
}
const number = isNumber(10); // number
const notNumber = isNumber('10'); // 'not number'

応用例

ここからは上記の型を利用することで出来る例をいくつか上げてみます。

例1.Genericsを使った関数の例

function reverse<T>(array: Array<T>): Array<T> {
  if (array.length > 0) {
    return [...reverse(array.slice(1)), array.shift() as T];
  } else {
    return [];
  }
}

数値の配列型になる
f:id:f-a24:20210114181551p:plain

例2.合併型とリテラル型を組み合わせることで選択肢を絞り込む

type TrafficLight = 'red' | 'yello' | 'blue';
function trafficLight(light: TrafficLight) {
  if (light === 'red') return 'Red';
  if (light === 'yello') return 'Yello';
  return 'Blue';
}

コーディング中に選択肢は補完される
f:id:f-a24:20210114182148p:plain

例3.API名から結果の型を抽出する

type APIName = 'a' | 'b';
type AResultType = 'A RESULT';
type BResultType = 'B RESULT';
type ResulutType<T> = T extends 'a' ? AResultType : BResultType; 
type APIType = <T extends APIName>(apiName: T) => Promise<ResulutType<T>>;

const api: APIType = apiName => new Promise(resolve => {
  setTimeout(() => {
    const resulet: any = apiName === 'a' ? 'A RESULT' : 'B RESULT';
    resolve(resulet);
  }, 1000);
});

(async () => {
  const resA = await api('a');
  const resB = await api('b');
  console.log(resA, resB);
})();

API名で結果の型が分かる
f:id:f-a24:20210114182447p:plain

TypeScriptでライブラリを使う

ReactjQueryなどの有名なライブラリは大体、@types/react@types/jqueryのような形で型定義ファイルが提供されているのでnpm i -D @types/reactのようにして使うのが一般的です。

型定義ファイルがあるかどうかはTypeSearchを使うと調べることができます。

jQueryの場合

import jQuery from 'jquery';

let $div: jQuery<HTMLDivElement> = $('#id');

型定義ファイルが無い場合は以下のように自分で定義する必要がありますので、TypeScript環境ではライブラリの選定基準として型定義ファイルがあるかどうかも重要になってきます。

// グローバル環境に読み込まれているライブラリの場合
declare global {
  interface Window {
    globalLibrary: any;
  }
}

// jQueryプラグインの場合
interface jQuery {
  pluginA: (arg: any) => jQuery
}

まとめ

自分が最初にTypeScriptに触れた時は、まだ言語自体が成熟していなかったことや慣れていないこともあり非常に苦労した記憶があります。

しかし、現在では言語自体やエディタの進化もあり非常に開発体験が良く正直なところ、もう普通のJavaScriptを書きたくないレベルでTypeScriptを気に入っています。

今回はTypeScriptの基本的な部分からちょっとした応用例まで紹介しましたが他にも機能やテクニックはまだまだあるので、今後も追求していきたいと思います。

JavaScriptがある程度書けるのであれば、学習コストはあまり高くなく、コーディングしていくうちに自然に身に付くと思いますので、ぜひ触れてみてください。

参考

本記事の執筆にあたり、以下の記事を参考にしました。

Atsushi Fujisawa カテゴリーの記事一覧 -