React JSX with TypeScript(1.6)

## はじめに

つい先日, [TypeScript 1.5系の安定版がリリース](http://blogs.msdn.com/b/typescript/archive/2015/07/20/announcing-typescript-1-5.aspx)されたばかりだが, 1.6系の話をしようと思う.

6月頃に「TypeScriptにReactのJSXサポートが載るぞ!」とアナウンスがあり, 「へー」と思っていたのだが, 久々にmasterを確認してみたら, 既にmergeされてたので触ってみた. 
(Roadmapへの記載とmergeのタイミングにあまり差がなかった模様[^1])

折角なのでTypeScriptのJSXサポートを試してみましたよ、というお話.  なお、作成した内容は[Quramy/tsc-react-example](https://github.com/Quramy/tsc-react-example)に上げてます.

#### *追記*
このエントリを書いた直後に気づきましたが, 既に[TypeScript の JSX/React サポートを試す](http://qiita.com/kimamula/items/11873444e6a4df19df37)に同様の内容が投稿されてました... orz

## 環境の準備

コードの話をする前に, TypeScriptでJSXを記述するための環境準備について.

なにはともあれ, TypeScript本体をレポジトリから持ってこないと話になら無い.
グローバルインストールする場合, 下記でnightlyのTypeScriptを持ってくる

```sh
npm install -g typescript@1.6.0-beta
```

```
tsc --version
```

で `Version 1.6.0-beta` として、1.6系の値が確認できればOK.

ローカルインストールであれば, ↓ですね.

```sh
npm install typescript@1.6.0-beta --save-dev
```

以下の説明ではローカルインストールした前提で進めることとする.

### tsconfig.json or tscのオプション

コマンドラインからtscでコンパイルする場合は, 下記のように`--jsx`オプションに`react` を付与して叩けばよい

```sh
./node_modules/TypeScript/bin/tsc --jsx react --module umd main.tsx
```

一点注意すると, tscに渡すファイルは`.ts`ではなく, `.tsx`である.
`.ts`だと, `--jsx` オプションを与えていてもJSXのパーサーは動作しない.

`--jsx`がコマンドラインオプションになっているということは, 当然tsconfig.jsonにも記載できるということなので, 以下のようにしてもよい.

```json:tsconfig.json
{
  "compilerOptions": {
    "module": "umd",
    "jsx": "react"
  }
}
```

### gulp-typescriptを使う場合

[gulp-typescript](https://github.com/ivogabe/gulp-typescript)を使うと, ローカルインストールしたTypeScriptとtsconfig.jsonを渡すことができるので, 僕は手元で下記のgulpfile.jsを作り, `gulp bundle`を実行していた.

```js:gulpfile.js
'use strict';

var gulp        = require('gulp'),
    typescript  = require('typescript'),
    ts          = require('gulp-typescript'),
    browserify  = require('browserify'),
    source      = require('vinyl-source-stream'),
    del         = require('del')
    ;

var project = ts.createProject('src/tsconfig.json', {typescript: typescript});

gulp.task('compile', function () {
  var result = gulp.src('src/**/*{ts,tsx}')
    .pipe(ts(project));
  return result.js.pipe(gulp.dest('.tmp'));
});

gulp.task('bundle', ['compile'], function () {
  var b = browserify('.tmp/bootstrap.js');
  return b.bundle()
    .pipe(source('bundle.js'))
    .pipe(gulp.dest('dist'))
  ;
});

gulp.task('clean', function (done) {
  del(['.tmp'], done.bind(this));
});
```

browserify周りはもうちょっと綺麗に書けそうな気もするが, あまり詳しくないので, これで許してください.

### react.d.ts

いつも通りdtsmでgetしておくだけ.

```sh
dtsm init
dtsm install react/react.d.ts --save
```

### エディタ
僕はVim使いなので, Vim用設定の話だけ書きます.AtomとかSublimeは知らん.

JSXでも補完を有効にしたいので, Vimプラギン[tsuquyomi](https://github.com/Quramy/tsuquyomi)を使うこととする. 
このプラギンは, ローカルインストールされたTypeScriptを優先的に利用して補完情報を取得するようにできているので, グローバルな環境を汚さずにTypeScriptの新機能を利用したいときにうってつけだ. 作成者本人が言うんだから間違いない.

なお, 前述したようにJSXオプションを利用するためには, 対象コードの拡張子が`.tsx`であるため, FileTypeの指定を.vimrcに追記しておく必要がある.

```vim:~/.vimrc
NeoBundle 'Shougo/vimproc'
NeoBundle 'Quramy/tsuquyomi'
NeoBundle 'leafgarland/typescript-vim'

autocmd BufNewFile,BufRead *.ts     set filetype=typescript
autocmd BufNewFile,BufRead *.tsx    set filetype=typescript
```

ご覧の通り, ちゃんとJSX中でも補完が効きました. TSServerすげー.

![tsx-react-example-capt01.png](https://qiita-image-store.s3.amazonaws.com/0/8808/0bd83eaf-36bd-9b4c-29ab-9d897e3efd54.png)

なお, syntaxプラギンはJSX対応されているわけではないが、特にハイライトの結果に違和感は感じない.

## 実際に作成したブツ
これでJSXを作成する準備が整った. 今回はサンプルということでtodo的なやつを作ってみた.

Babel + React等で0.13系のJSXを書いたことがあれば, 殆ど違和感なくComponentを作ることができる.

Babelとの差異で言うと, ReactのComponentをextendsする際に, ジェネリクスで2つのtypeが要求される、という点だろう.
1つめがprops用, 2つ目がstate用のtypeである.

* 本体

```typescript:src/main.tsx
/// <reference path="../typings/bundle.d.ts" />

import * as React from 'react';
import {TodoItem} from './todoItem';

interface ITodo {
    description: string;
    key: number;
}

export interface IMainState {
    newItem?: {
        description: string;
    };
    todoList?: ITodo[];
}

export interface IMainProps {}

export class Main extends React.Component<IMainProps, IMainState> {

    state: IMainState = {newItem: {description: ''}, todoList: []}

    constructor () {
        super();
        this.changeName = this.changeName.bind(this);
        this.addItem = this.addItem.bind(this);
        this.removeItem = this.removeItem.bind(this);
    }

    changeName (e: any) {
        this.setState({
            newItem: {
                description: e.target.value
            }
        });
    }

    addItem () {
        var list = this.state.todoList;
        list.push({
            description: this.state.newItem.description,
            key: new Date().getTime()
        });
        this.setState({
            todoList: list,
            newItem: {description: ''}
        });
    }

    removeItem (item: ITodo) {
        var list = this.state.todoList.filter(i => i.key !== item.key);
        this.setState({todoList: list});
    }

    render () {
        var todoItems = this.state.todoList.map(item => {
            return <TodoItem key={item.key} item={item} onRemove={this.removeItem} ></TodoItem>;
        });
        return (
            <div>
                <div>
                    <input type="text" placeholder="input new item" value={this.state.newItem.description} onChange={this.changeName} />
                    <button onClick={this.addItem} >add</button>
                </div>
                <ul>{todoItems}</ul>
            </div>
        );
    }
}
```

* リストの要素

```typescript:src/todoItem.tsx
/// <reference path="../typings/bundle.d.ts" />

import * as React from 'react';

interface ITodo {
    description: string;
}

export interface ITodoItemState {}

export interface ITodoItemProps {
    item: ITodo;
    onRemove?: (todo: ITodo) => any;
    key?: number; // I think this prop is unnecessary, but unless it an error occurs in tsc.
}

export class TodoItem extends React.Component<ITodoItemProps, ITodoItemState> {
    constructor () {
        super();
        this.removeItem = this.removeItem.bind(this);
    }

    removeItem () {
        this.props.onRemove(this.props.item);
    }

    render () {
        return (
            <li>
              <span> {this.props.item.description} </span>
              <button onClick={this.removeItem} >delete</button>
            </li>
        );
    }
}
```

* 起動するやつ(bundle.jsの起点)

```typescript:src/bootstrap.ts
/// <reference path="../typings/bundle.d.ts" />

import * as React from 'react';
import {Main} from './main';
React.render(React.createElement(Main), document.getElementById('main'));
```

* HTML

```html:dist/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>tsc-react-example</title>
</head>
<body>
<div id="main"></div>
<script src="bundle.js"></script>
</body>
</html>
```

## 気になったところ
仮想DOMエンジン側に要素とDOMのひもづけを教えてあげた方が描画が早くなる関係上, ReactでリストをRenderするときは以下のように `key=...` を指定する.
(指定しないとReactがwarning吐いてくる)

```typescript
var todoItems = this.state.todoList.map(item => {
    return <TodoItem key={item.key} item={item} onRemove={this.removeItem} ></TodoItem>;
});
```

ただ, 上記のコードを書いた所, `TodoItem` クラスのprops interfaceにもkeyという名前のプロパティが無いと怒られた.

```typescript
class TodoItem extends React.Component<{key: number;}, any> {...}
```

`key`ってComponentのpropsとして扱うものなのか？という違和感を少し感じたので書き留めておく.


## 終わりに
環境準備も含めて, かなりあっさりJSX + TypeScriptを用意することができた.
実際にガリガリと.tsxを書くのは1.6がリリースされてからになるだろうけど, React使いの人は今のうちからチェックしておいたらどうだろう。

[^1]: [Roadmapへの記載は6/19](https://github.com/Microsoft/TypeScript/wiki/Roadmap/c5032f4211357aa61db20bbfc3e5b8e8746f32a3), [mergeされた](https://github.com/Microsoft/TypeScript/pull/3564#event-343293342)が6/30 っぽい