TypeScript 2.9 変更点

こんにちは。LINEの[@uta_tti](https://twitter.com/uta_tti)です。

今日TypeScript 2.9がリリースされました。2.8のconditional typesと比べるとちょっと破壊力が足りない気もしますが、個人的に待っていた機能が入ったりしたので、紹介したいと思います。今回のアップデートではCLIやツール系の改善も多かったですが、言語自体の変更点だけ紹介します。詳しくは公式記事を参考してください。

- [Announcing TypeScript 2.9
](https://blogs.msdn.microsoft.com/typescript/2018/05/31/announcing-typescript-2-9/)

もし内容(や日本語)などが間違ってたら教えてください。

# `import(...)`式からの型import

`import(...)`はES modulesを動的に読み込む式になります。「式」という単語を使っていますが、実際`import(...)`は関数ではなく文法要素です。なので`typeof import`しても結果は出ませんし、第一級として扱うことも出来ません。

`import(...)`は現在ES仕様のステージ3であり、もうChromeなどでは実装されています。

- [TC39 `import()` proposal](https://github.com/tc39/proposal-dynamic-import)

TypeScriptでももちろん`import(...)`自体は使えましたが、`import(...)`から型はimport出来なかったため、不自然にtop-level importを混ぜて使うしかありませんでした。

```typescript
import { SomeType } from "./foo"

async function func() {
  let val: SomeType

  // ...

  val = (await import("./foo")).someValue
}
```

TypeScript 2.9 では`import(...)`が型情報も提供してくれるので、以下のように書けます。

```typescript
async function main() {
  let val: import("./foo").SomeType

  // ...

  val = (await import("./foo")).someValue
}
```

# JSON import

今まではJSONファイルは`require(...)`を使ってimportするしかなく、型はいつも`any`でした。

```typescript
let pkg = require("./package.json") // any
pkg.name // any
```

TypeScript 2.9からはJSONファイルにも`import`を使えるようになり、型もつけてくれます。

```typescript
import pkg from "./package.json"
pkg.name // string
```

# `keyof`型の変更

今まで`keyof`は文字列ベースのキーにしか対応しませんでした。

```typescript
interface X {
  str: any,
  1: any,
  ‪[symbol]: any
}

keyof X // "str" | "1"

keyof any[] // "map" | "length" | ...
```

TypeScript 2.9からは`number`や`Symbol`などのキーにも対応し、`Array`の場合ちゃんと`number`もキーに含めてくれます。

```typescript
interface X {
  str: any,
  1: any,
  ‪[symbol]: any
}

keyof X // "str" | 1 | symbol

keyof any[] // number | "map" | "length" | ...
```

上の変更で、例えば`keyof X`の型の変数に`"1"`を入れるとエラーになります。破壊的な変更なので、対応が必要な場合があります。もし今まで`string`をとあるオブジェクトのキーの型として使ってたとしたら、`string`の代わりに以下のように型を宣言して使うとよいです。

```typescript
type ObjectKey = keyof any // instead of `string`
```

もしTypeScript 2.8までのように文字列ベースの動作が欲しい場合、`compilerOptions`に`keyofStringsOnly`が新しく追加されましたので、これを`true`にすると以前と同じ動作でコンパイルしてくれます。

# Tagged template literalsの型変数サポート

Tagged template literalsとは以下のようにとある関数をtemplate literalのtagとして使うものです。

```typescript
func`hello${x}world${y}yay`;
```

上の式は実際以下のように書くのと同じです。

```typescript
func(["hello", "world", "yay"], x, y);
```

Tagged template literalsについて詳しくは下のMDN文書を参考してください。

- [MDN: Template literals - Tagged templates](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_templates)

上の例で確認できるよう、tagged templateのtagは普通の関数です。TypeScriptでは関数に型変数を提供してpolymorphicな動作をさせることが出来ます。

```typescript
function func<X, Y>(strs: TemplateStringsArray, x: X, y: Y)

func<string, number>([...], "hello", 10);
```

しかしtagged templateの場合それが出来ませんでした。

```typescript
func<string, number>`hello${x}world${y}yay`; // syntax error
func`hello${x}world${y}yay`; // x: any, y: any
```

TypeScript 2.9では、tagged templateでも型変数を使えるようになりました。

```typescript
func<string, number>`hello${x}world${y}yay`; // x: string, y: number
```

この機能を一番まっていたのは[styled-components](https://github.com/styled-components/styled-components/pull/1777)ではないかと思います。styled-componentsはcomponent定義でcustom propsも使えるようにしていますが、TypeScriptではcustom propsの型を提供することが出来ませんでした。

```typescript
const MyH1 = styled.h1`
  color: ${props => props.customProp}
`; // type error, no `customProp` in props
```

それをTypeScript 2.9では以下のように提供できるようになります。

```typescript
type MyH1Props = { customProp: string };

const MyH1 = styled.h1<MyH1Props>`
  color: ${props => props.customProp}
`;
```

実は上のコードはstyled-componentsに型バグがあるためまだ動きません。その修正をpull requestで出しておきました。

- https://github.com/styled-components/styled-components/pull/1777

# その他

その他、マイナーな変更点が何個かありますので、気になる方は公式記事を見てみてください。