地獄からの使者、その名はTypeScript ～ 固かった型の形 ～ 2

# 地獄からの使者、その名はTypeScript ～ 固かった型の形 ～

　TypeScriptは便利である。変な書き方をしていれば教えてくれるし、開発環境が入力補完の支援もしてくれる。まさにヘブンである。しかし書き方を知らないと、その世界は地獄と化す。真っ赤なエラーに焼き尽くされた地獄に。

　地獄を回避するためには解決法を知ることである。TypeScriptは今も刻一刻と進化しており、昨日の知識は原始の人間が棒の握り方を覚えたに等しい。

　ということで地獄を渡り、天国へ到達するための知恵の一部を紹介していきたいと思う。たぶん他にもあるような気がするのだが、TypeScriptを使い始めてだいぶ慣れてしまったので、何に困ったのか全てを覚えていない。思い出したら、記事を追記して投稿していこうと思う。こういう場合にどう解決するべきなのかという疑問があれば、それを考えていきたい。

## 1. JavaScriptからTypeScript移行時に最初に訪れる試練、連想配列

　これを一度もやらずにTypeScriptを使っている者がいたとすれば驚愕である。JavaScriptからTypeScriptに移行する場合に、最初に放ってくる地獄からの息吹である。これに心を折られ、踵を返してしまうものもいることだろう。



- 失敗例  
プロパティの存在しないオブジェクトが作られ、aが入らない

```.ts
const value1 = {}
value1.a = 10 //ここでエラー
console.log(value1.a)
```

- 修正例  
連想配列にする
 
```.ts
const value1:{[key:string]:any} = {}
value1.a = 10
console.log(value1.a)
```

## 2. interfaceを使うほどでもないので、何度も同じ型を定義してしまう

　こうして型を付けるのがかったるいと思ってしまうのだ


```.ts
const value1: { a?: number, b?: number } = { a: 100 }
const value2: { a?: number, b?: number } = { b: 200 }
```

　typeofで型が再利用できる

```.ts
const value1: { a?: number, b?: number} = { a: 100 }
const value2: typeof value1 = { b: 200 }
console.log(value2.a) //コンパイルが通る
```

## 3. イキってstrictをtrueにしたときに訪れる試練、オブジェクトのキー

　valuesは **a|b|c** のキーしか持たないので、string型のkeyを突っ込むとエラーにされる。Object.keysから推論して欲しいところだが、現在はそうなっていない。

```.ts
const values =  {'a':100,'b':200,'c':300}
for(const key of Object.keys(values)){
	console.log(`${key}:${values[key]}`) //values[key]でエラー
}
```

　よく見かけるのはアンチパターンの対処法だ。型を吹き飛ばして無理やり引っ張り出す手法だ。テロリストを倒すのに、住民もろとも爆撃してしまうような暴挙と呼べる。この場合はkeyの型を正しく認識させなければいけない。typeofで型を取り出し、さらにkeyofでキーの型を取り出すという二重構造だ。

```.ts
const value2 = { 'a': 100, 'b': 200, 'c': 300 }
for (const key of Object.keys(value2)) {
	console.log(`${key}:${value2[key as keyof typeof value2]}`)
	//console.log(`${key}:${(value2 as any)[key]}`) //よくあるアンチパターン
}
```

※2019/05/23追記
　Object.keysの宣言を上書きして適切な型を返すようにすれば、地獄にいるのに清涼感を味わえる
　そもそもkeyを返しているのにstringが返る、元の定義が微妙なのではないだろうか？

```.ts
interface ObjectConstructor {
	keys<T>(o: T): (keyof T)[];
}
const value2 = { 'a': 100, 'b': 200, 'c': 300 }
for (const key of Object.keys(value2)) {
	console.log(`${key}:${value2[key]}`) //そのまま通る
}
```



## 4.1 intarface中のプロパティの型が欲しい場合

　interface自体を使えればよいのだが、中のプロパティの型が欲しいケース
　巨大な構造だったら再定義は本当の地獄だ

```.ts
interface AnyData{
	values:{
		a:number
		b:number}[]
   }
}

const proc = (values)=>{ //←ここで型を付けないといけない
	console.log(`a:${values.a} b:${values.b}`)
}
const anyData: AnyData = { values: [{ a: 100, b: 200 }] }
proc(anyData.values) //インタフェイスの中の一部分を渡す
```

　ちなみにインタフェイス中のプロパティの型を取得する方法は、「もしかしてこれがやりたいんでしょ」とVSCodeが教えてくれた
　VSCode先生、一生はついていかないけど適当なところまではついていきます！

```.ts
interface AnyData {
	value: {
		a: number
		b: number
	}
}
//const proc = (values: {a:number,b:number}) => { //再定義してしまう書き方
const proc = (values: AnyData["value"]) => { //元の定義を利用
	console.log(`a:${values.a} b:${values.b}`)
}
const anyData: AnyData = { value: { a: 100, b: 200 } }
proc(anyData.value)
```


## 4.2 intarface中のプロパティの型が欲しいのに ? が邪魔をしている場合

※ 2019/5/23 追記
　以前書いたプログラムの手直しをしていたら、こういうケースに遭遇した
　現時点のtsで型の否定(undefinedを除外)をする方法が導入されていない以上、詰んだかと思われたが何とかなった

```.ts
interface AnyData {
	value?: { //?がついていると、aやbの型にたどり着けなくなる
		a: number
		b: number
	}
}
const proc = (value: AnyData["value"]["a"]) => { //aが存在しないと怒られる
	console.log(value)
}
```

　オラ、この地獄でワクワクしてきたぞ

```.ts
interface AnyData {
	value?: { 
		a: number
		b: number
	}
}
const proc = (value: (AnyData["value"] & {})["a"]) => { //{}を詰め込む
	console.log(value)
}
```

 やった者勝ちみたいな世界だ


※ 2019/5/28 追記 recordareさんから
　-?を使った書き方

```.ts 
interface AnyData {
  value?: {
     a: number
     b: number
  }
}
type IgnoreOptional<T, K extends keyof T=keyof T> = {[P in K]-?: T[P]}[K];
const proc = (value: IgnoreOptional<AnyData["value"]>["a"]) => { 
    console.log(value)
}
```

※ 2019/5/28 追記 uhyoさんから
　NonNullableで除去可能だった

```.ts
interface AnyData {
	value?: { 
		a: number
		b: number
	}
}
const proc = (value: NonNullable<AnyData["value"]>["a"]) => {
    console.log(value)
}
```


## 4.3 分かれ道に差し掛かった場合

※ 2019/5/28 追記

　Extractで分かれ道も条件を指定可能
　もう、何でもありか・・・

```.ts
interface AnyData {
	value: {
		a: number
		b: number

    }|{
         c:number;
         d:number;
    }
}
const proc = (value: Extract<AnyData["value"],{"a":any}>["a"]) => { 
 	console.log(value)
}
```


## 5. プロパティがあるか確認しようとするとエラーになるケース

　これは解説記事が多いので、いまさらかもしれない

```.ts
//プロパティがあるか確認しようとするとエラー
interface A{
	a:number
}
interface B{
	b:number
}
const proc2 = (value: A | B)=>{
	if(typeof value.a !== 'undefined') //この時点でaが使えない
		console.log(value.a)
}
proc2({a:100})
```

```.ts
interface A {
	a: number
}
interface B {
	b: number
}
const proc2 = (value: A | B) => {
	if ("a" in value) //TypeScriptではこう書かないといけない
		console.log(value.a)
}
proc2({ a: 100 })
```

## 6. HTMLElementに追加プロパティを設定したい場合

　素のJavaScriptで気軽にやっていたこれは、エラーとなる

```.ts
const divElement = document.createElement('div')
divElement.numValue = 100 //HTMLDIVElementにnumValueが存在しないのでエラー
```

　よくあるアンチパターンがこれだ
　なんでもanyさえあれば解決という使い方
　anyは免罪符か何かだろうか？

```.ts
const divElement = document.createElement('div');
(divElement as any).numValue = 100 //アンチパターン
```

わざわざinterfaceを作るまでも無く、その場でプロパティを追加したければ以下のように設定する

```.ts
const divElement: HTMLDivElement & { numValue?: number } = document.createElement('div')
divElement.numValue = 100 
```

## 7. childNodesでclassNameなどが使えない場合

　childNodesの中に入っているのがChildNodeになっている
　素のJavaScriptからプログラムを持ってくると、かなりの確率で引っかかるポイントだ

```.ts
const divElement = document.querySelector('div')
if (divElement) {
	const childNodes = divElement.childNodes //これが NodeListOf<ChildNode> になっている
	for (let i = 0, length = childNodes.length; i < length; i++) {
		const node = childNodes[i]
		node.className = 'CustomClass' //これが使えない
	}
}
```

　HTMLElementにキャストしてやれば、関連プロパティにアクセスできる

```.ts
const divElement = document.querySelector('div')
if (divElement) {
	const childNodes = divElement.childNodes as NodeListOf<HTMLElement>
	for (let i = 0, length = childNodes.length; i < length; i++) {
		const node = childNodes[i]
		node.className = 'CustomClass'
	}
}
```

　きちんとチェックすれば as はいらない

```.ts
const divElement = document.querySelector('div')
if (divElement) {
	const childNodes = divElement.childNodes
	for (let i = 0, length = childNodes.length; i < length; i++) {
		const node = childNodes[i]
		if (node instanceof HTMLElement)
			node.className = 'CustomClass'
	}
}
```

# 地獄から天国への道

　TypeScriptの地獄から、天国へいたる道は長く険しい。そして何人もの人間がエンマ大王に舌を抜かれ沈黙する。しかしそこを抜けた先には、必ずやヘブンがあるはずだ。釜茹でにされようが、針で刺されようが、地道に賽の河原で石を積み上げていくしか方法はないのである。

　地獄を知らぬ者に、天国を感じることはできないのだから。