TypeScript:型

型指定

変数宣言時

let hoge: string = 'aa';

型エイリアスを使う

type Hoge = string;
let hoge:Hoge = 'aa';

type name = string;
type age = number;
type person = [name, age];
const pesron1:person = ['taro', 20];

↓以下のようにまとめてもかける

type person = [name:string, age:number];
const pesron1:person = ['taro', 20];

配列

type hoge = number[];
type hoge = string[];
type hoge = (string|number)[];

※T[] と Array<T>は同じ
※any[] はその配列が定義されたスコープから離れるとそれ以上拡張できない最終的な型を割り当てる

タプル(配列のサブタイプ(派生型))

type hoge = [string, string, number];
type hoge = [string, ...string[]]; // 可変長もつくれる
type hoge = [string, boolean, ...string[]];// 可変長もつくれる2

配列の中身まで変更不可

プリミティブ型は const で宣言すればよいが、参照型はこのやり方で。
(いくつか書き方がある)

type hoge = readonly string[];
type hoge = ReadonlyArray<string>;
type hoge = Readonly<string[]>;

タプル

type hoge = readonly [number, string];
type hoge = Readonly<[number, string]>;

※Readonly<データ型>はユーティリティの使い方みたい
ユーティリティの後に<>でデータ型を指定して、設定を付加

type data = [number,string];
type req_data = Readonly<data>;
const data1:data = [2,'hoge'];
const data2:req_data = [3,'fuga'];
data1[0]=3;
data2[0]=3; //こっちはエラー

null, undefined, void, never

あとで書く

enum型

enum型で宣言したものも type として扱える

const enum Language {
  English='English',
  Spanish='Spanish',
  Japanese='Japanese',
}
// 値は連番で推論されてしまうので指定する
// const で宣言することで逆引き(値からひく)できなくなる
// enumの中に1つでも数値があるとすべての数値がenumに割り当て可能になるので、
// 値はすべて文字列で持とう

function hoge(lang:Language){
  return lang;
}

リテラル

type a = “ok”;

オブジェクトリテラル

type hoge = {
  b:number
}

指定なしを許す

type hoge = {
  b:number
  c?:string
}

▼インデックスシグネチャを使う

type Hoge = {
  [key: number]: boolean
}
[key:T]: U の形で書く。
Tはstringかnumber となる。
key はなんでも良い

type Hoge = {
  [key: string]: boolean
}
let hoge:Hoge = {
  aa:'cc'
}
hoge[100] = 'cc'; // キーは100が文字列に勝手になるがこの指定もいける

▼マップ型
1つのオブジェクトに最大1つもてる

type Weekday = 'Mon'|'Tue'|'Wed'|'Thu'|'Fri'
type Hoge = {
  [K in Weekday]: string
}
// Weekday にあるものが欠けているとエラー

// keyof演算子とルックアップ型と組み合わせる★よく使いそう!
type Account = {
  id: number
  is_a: boolean
  notes: string[]
}
type OptionalAccount = {
  [K in keyof Account]:Account[K]
}

// すべてのフィールドを省略可能
type OptionalAccount = {
  [K in keyof Account]?:Account[K]
}

// すべてのフィールドをnull 許容
type OptionalAccount = {
  [K in keyof Account]: Account[K] | null
}
// こういう書き方もあるみたい。(type 指定のところに基本型を記載)
type Hoge = {
  [key in string]:string 
}
const hoge:Hoge = { 'aa':'bb', };

▼マップ型 with Enum

enum color {red = 'red', blue = 'blue'}
type Pen = {
  [key in color]:string
}
const hoge:Pen = {
  red:'aaa',
  blue:'bbb' // 片方欠けるとエラー
  // green:'ccc' // enum にないものもエラー
}

▼レコード型
ユーティリティ型の1つ。
type 型名 = Record<キー | 値>

type prop_name = 'hoge' | 'fuga';
type Abc = Record<prop_name, string|number>

const abc:Abc = {
  hoge:'aa',
  fuga:12
}
// すべてのキーに同じ型が適用される
// すべてのキーがそろってないとエラー

▼ Pick型

変数 = Pick<型, キー>

type all_data = {
  aaa:string,
  bbb:string,
  ccc:string,
  ddd:number
}
type pick_keys1 = 'aaa' | 'bbb'
type pick_keys2 = 'aaa' | 'bbb' | 'ddd'
type picked1 = Pick<all_data, pick_keys1>
type picked2 = Pick<all_data, pick_keys2>
const picked_example1:picked1 = {
  aaa:'aa',
  bbb:'bb'
}
const picked_example1:picked1 = {
  aaa:'aa',
  bbb:'bb',
  ddd:3
}

▼その他、組み込みのマップ型

Partial<Object>
  Object内のすべてのフィールドを省略可能と指定

Required<Object>
  Object内のすべてのフィールドを必須(省略不可)と指定

Readonly<Object>
  Object内のすべてのフィールドを読み取り専用と指定

▼ルックアップ型
キーを指定して型を取得する

type ApiResponse = {
  user:{
    id:string,
    friend_list:{
      count:number,
      friends:{
        first_name:string
        last_name:string
      }
    }
  }
}
type FriendList = ApiResponse['user']['friend_list'] 
// ドット記法は使えないので注意

▼keyof 演算子
オブジェクトのすべてのkeyを文字列リテラル型の合併型として取得できる

type user_keys = keyof ApiResponse['user']

▼各値に変更不可設定
オブジェクトのプロパティに対する const のようなもの

type hoge = {
  readonly b:number
}
// interface でも使える

合併型・交差型

▼合併型:リテラルを使う

type result_str = "ok" | "ng";
const result_str:result_str  = "ok";

▼合併型:基本型を使う

type id = string | number;
const _id:id = 'hoge';

▼合併型・交差型:オブジェクトリテラルで使う

type Cat = {name: string, purrs: boolean}
type Dog = {name: string, barks: boolean, wags: boolean}
type CatOrDogOrBoth = Cat | Dog
type CatAndDog = Cat & Dog

// CatOrDogOrBoth はどちらかのメンバーがそろう、もしくは両方のメンバーがそろうがOK
// CatAndDog は両方のメンバーが全部そろう必要がある

合併型はよくつかう
type Returns = string | null

▼クラスでも使える

class Hoge {
  ***
}
class Fuga {
  ***
}
type UnionExample = Hoge | Fuga;
const aaa:UnionExample = new Hoge();
const bbb:UnionExample = new Fuga();
const data:UnionExample[] = [aaa,bbb];

nullかもしれない

type person = [name:string, age?:number];
const pesron1:person = [‘taro’, 20];
const pesron2:person = [‘taro’];
※?を指定した後に?のない項目はもってこれない。
※!を指定すると、絶対に null ではない

関数の型(呼び出しシグネチャ、型シグネチャ)

▼省略記法

type Hoge = (a: number, b:number) => number

// コールバック関数がパラメータにある場合
type Hoge = (f:(index:number)=> void, n:number) => void

▼完全な呼び出しシグネチャ

type Hoge = {
  (a:number, b?:number):void
}

// オーバーロードされた関数(複数の呼び出しシグネチャを持つ関数)
type Hoge = {
  (from:Date, to:Date, aaa:string):Fuga
  (from:Date, aaa:string):Fuga
}
// 実装のシグネチャは手動で結合、内部も呼び出され方をチェックしないといけない
let hoge:Hoge = (from:Date,to_or_aaa:Data|string,aaa?:string) =>{
  if(to_or_aaa instanceOf Date && aaa !== undefind){
    // 処理
  }else{
    // 処理
  }
}

ジェネリック型を関数で使う

▼宣言できる位置

// 1:hogeを呼び出すときにTにバインドされる
type Hoge = {
  <T>(arr:T[], f:(item:T)=>boolean): T[]
}
let hoge:Hoge = ...

// 2:hoge を定義するときにTをバインド
type Hoge<T> = {
  (arr:T[], f:(item:T)=>boolean): T[]
}
let hoge:Hoge<number> = ...

// 3:hogeを呼び出すときにTにバインドされる
// 1の省略記法ver
type Hoge = <T>(arr:T[], f:(item:T)=>boolean) => T[]
let hoge:Hoge = ...

// 4:hoge を定義するときにTをバインド
// 2の省略記法ver
type Hoge<T> = (arr:T[], f:(item:T)=>boolean) => T[]
let hoge:Hoge<number> = ...

// 名前付き関数の呼び出しシグネチャ
// hoge を呼び出すたびにTに対してそれぞれバインド
function hoge<T>(arr:T[], f:(item:T)=>boolean):T[]{
  // ...
}

▼ジェネリックを明示的にアノテートする
渡されたパラメータをみてジェネリック型は推論されるが、明示的に指定できる

hoge<string>(['aa','bb'], item => !!item))

// 推論できないときはアノテートする必要がある
// ↓ Promise のジェネリックパラメータに明示的にアノテートが必要
let promise = new Promise<number>(resolve => resolve(45));
promise.then(result => result *4);

型エイリアスでジェネリック型を使う

// 型エイリアスの中で唯一指定できる場所
type MyEvent<T> = {
  target: T,
  type: string
}

// 型を使うときに型パラメータをバインドする必要がある
let my_event:MyEvent<HTMLButtonElement | null> = {
  target:document.querySelector('#myButton'),
  type:'click'
}

// 関数シグネチャの中で使う
function trrigerEvent<T>(event:MyEvent<T>):void {
  //...
}
trrigerEvent({
  target:document.querySelector('#myButton'),
  type:'mouseover'
})

制限付きポーリフィズム

ポリモーフィズムとは、それぞれ異なる型に一元アクセスできる共通接点の提供、またはそれぞれ異なる型の多重定義を一括表現できる共通記号の提供を目的にした、型理論またはプログラミング言語理論の概念および実装である

▼少なくとも Hoge でなければならない

type Hoge = {
  value:string
}
type HogeTypeA = Hoge & {
  is_a:true
}
type InnerHoge = Hoge & {
  children: [Hoge] | [Hoge, Hoge]
}

let base:Hoge = {value:'hoge'}
let type_a:HogeTypeA = {value:'hoge_a', is_a:true}
let inner:InnerHoge = {value:'innner',children:[type_a]}

// T は、Hoge か Hoge のサブタイプ
// T が extends Hoge とすることで サブタイプの型がreturn 後も保持される?
function mapHoge<T extends Hoge>(
  hoge:T,
  f:(value: string) => string
): T {
  return {
    ...hoge,
    value:f( f(hoge.value))
}

▼複数の制約をつける

type Width = {width: number}
type Height = {height: number}
function logArea<Shape extends Width & Height>(s: Shape):Shape{
 console.log(s.width * s.height);
  return s;
}
type Square = Width & Height;
let square:Square = {width:30,height:20}
logArea(square)

▼可変長引数

function call<T extends unknown[], R>(
  f:(...args: T) => R,
  ...args: T
): R {
  return f(...args)
}
function fill(length:number, value:string):string[]{
  return Array.from({length}, ()=>value)
  // Array.from(arrayLike[, mapFn[, thisArg]])
  // arrayLike = 配列のようなオブジェクト (length プロパティおよびインデックス付けされた要素を持つオブジェクト) 
}
call(fill, 10, 'a');
// ...args として渡された引数から T がどのようなタプル型か推論
// Rは、f(fill)が返すのと同じ型をバインド

ジェネリック型でデフォルトの型を指定

type MyEvent<T= HTMLElement> = {
  target:T,
  type:string
}

// 制限付きのときも使える
type MyEvent<T extends HTMLElement = HTMLElement> = {
  target:T,
  type:string
}

※関数のオプションパラメータと同様に、デフォルトの型を持つジェネリック型は、デフォルトの型を持たないジェネリック型の後に指定する必要がある(=つまり未指定が可能なものは後にもってくる!)

テンプレートリテラル型

実は便利ですよ。

type result = "ok" | "ng";
type msg_tmpl = `${result}_aa`;
type msg_tmpl2 = `${result}_bb`;
const aa:msg_tmpl = 'ok_aa';
const bb:msg_tmpl2 = 'ng_bb';

インターフェースとの違い

▼型でもインターフェースでも書ける

type Hoge = {
  aa:string
  bb:number
}
type HogeCc = Hoge & {
  cc:boolean
}
type HogeDd = Hoge & {
  dd:boolean
}  
// ↑の型宣言をインターフェースで書き直すと
interface Hoge {
  aa:string
  bb:number
}
interface HogeCc extends Hoge {
  cc:boolean
}
interface HogeDd  extends Hoge {
  dd:boolean
}

▼型でしか書けない

type Hoge = number
type Fuga = Hoge | string

▼インターフェースのextends は拡張できるかのチェックが入る

interface A {
  aa(x:number):string
  bb(x:number):string
}
interface B extends A {
  aa(x:string | number):string // こっちは元のnumberがあるからOK
  bb(x:string):string // こっちは元のnumberがないからエラー
}

▼宣言のマージ

// OK
interface A {
  name:string
}
interface A {
  age:number
}
let a:A = {
  name:'hoge',
  age: 30
}

// typeは重複宣言できない
type A = {
  name:string
}
type A = {
  age:number
}

▼インターフェースにもジェネリック型を指定できる

interface Hoge<K, V>{
  get(key:K):V
  set(key:K, value: V):void
}

型の絞り込み

TypeScriptはフローベースの型推論をする

hoge == null // hoge が null か undefined と判断する

typeof hoge それがなの型か判断する

outerFunction(hoge) 関数の戻しとして指定されているいずれかと判断する

▼合併型を渡すパターンは区別するためのリテラル型のタグを持つべき
タグとして持つ条件は、リテラル型、ジェネリック型ではない、互いに排他的であること

type TextEvent = {
  type:'text_event'
  value: string,
  target: HTMLInputElemnt
}
type MouseEvent = {
  type:'mouse_event'
  value: [string,number]
  target: HTMLElemnt
}
type AllEvent = TextEvent | MouseEvent; 
function handle(event:AllEvent):void {
  if(event.type === 'text_event'){
    // もしもここで、 typeof event.value === 'string' でチェックすると→
    event.value // string
    event.target // HTMLInputElement
    return;
  }
  // →合併型は重複の可能性もあるので、確実に判断できるとは言えず
  // この段階で TextEvent のタイプがくる可能性も捨てきれない
  event.value // [string,number]
  event.target // HTMLElemnt
}

型ガード

TypeScriptが型を判定するやり方は限られる。

hoge[lookupkey] が string もしくは number の可能性があるときに
if(typeof hoge[lookupkey] === 'string'){
// hoge[lookupkey] が string である保証がないので、
// hoge[lookupkey].match でエラーになる

↓いったん変数で受けて型を確定させる必要がある。

const hoge_lookupkey = hoge[lookupkey];
if(typeof hoge_lookupkey === 'string'){
// ここで hoge_lookupkey.match を使える

// これははまった…!めんどくせえええええ

ユーザ定義型ガード

型の絞り込みは現在のスコープ内でしか有効ではないので、型判定をTypeScriptに is 演算子で伝える

function isString(a:unknown):a is string {
  return typeof a === 'string';
}
// この書き方で返り値は boolean 扱い

条件型

type IsString<T> = T extends string
  ? true
  : false
type A = IsString<string> // true
type B = IsString<number> // false

▼組み込みの条件型

// Exclude<T, U>
// Tに含まれているが、Uに含まれていない型
type A = number | string
type B = string
type C = Exclude<A, B> // number

// Extract<T, U>
// Tに含まれている型のうち、Uに割り当てることができるもの
type A = number | string
type B = string
type C = Extract<A, B> // string

// NonNullable<T>
// nullとundefined を除外したTのバージョンを計算
type A = {a?: number | null}
type B = NonNullable<A['a']> // number

// ReturnType<F>
// 関数の戻り値の型(ジェネリックやオーバーロードされた関数は期待どおりに動作しない
type F = (a:number) => string
type R = ReturnType<F> // string

// InstanceType<C>
// クラスコンストラクターのインスタンス型を計算
type A = {new():B} // あれ、これなんだ…
type B = {b: number}
type I = InstanceType<A> //{b: number}

infer

これは後でもう1回もどって確認

type SecondArg<F> = F extends (a:any, b:infer B) => any ? B : never;
type F = typeof Array['prototype']['slice']
type A = SecondArg<F> // number | undefinde

型アサーション

安全ではないので使用は控える

function Hoge(param:string){
  // ...
}
funtion getParam():string|number{
  // ...
}
let param = getParams()

// paramが文字列だと主張
Hoge(param as string)
// もしくは
Hoge(<string>params)

▼非nullアサーション
T | null または T | null | undefined の型に対して、 null | undefined ではないことを伝える構文
(ただし、多用している場合は要リファクタリングの兆候)

document.getElementById(dialog.id!)!
// dialog.id と document.getElementByIdの呼び出しの結果が定義済みと伝える

▼明確な割り当てアサーション
(ただし、多用している場合は要リファクタリングの兆候)

let userId!:string
// ↑の!マークで、読み取られるときには値が確実にアサインされていることを伝える
fetchUser()
userId.toUpperCase()

function fetchUser(){
  userId = globalCache.get('userId');
}

コンパニオンオブジェクトパターン

型とオブジェクトをペアにする

type Unit = 'mm' | 'cm' | 'mm' | 'km'
type Hoge= {
  unit:Unit
  value: number
}
let Hoge= {
  from(value:number, unit:Unit):Hoge{
    return {
      unit,
      value
    }
  }
}

import {Hoge} from './Hoge' // ここのHogeはtype であり、 Objectでもある
let Fuga:Hoge = {
  unit:'mm'
  value:10
}
let other = Hoge.from(10,'m')

名前的型(型のブランド化)

type CompanyId = string & {readonly bland: unique symbol}
type OrderId = string & {readonly bland: unique symbol}

function CompanyId(id:string){
  return id as CompanyId;
}
function OrderId (id:string){
  return id as OrderId ;
}

function getCompnayInfo(CompanyId ){
  // ...
}
let CompanyId  = CompanyId('aaaa')
let OrderId  = OrderId ('bbbb')
getCompnayInfo(CompanyId) // OK
getCompnayInfo(OrderId) // NG