JavaScript
TypeScript
reactjs

React JSX with TypeScript(1.6)

More than 3 years have passed since last update.


はじめに

つい先日, TypeScript 1.5系の安定版がリリースされたばかりだが, 1.6系の話をしようと思う.

6月頃に「TypeScriptにReactのJSXサポートが載るぞ!」とアナウンスがあり, 「へー」と思っていたのだが, 久々にmasterを確認してみたら, 既にmergeされてたので触ってみた.

(Roadmapへの記載とmergeのタイミングにあまり差がなかった模様1)

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


追記

このエントリを書いた直後に気づきましたが, 既にTypeScript の JSX/React サポートを試すに同様の内容が投稿されてました... orz


環境の準備

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

なにはともあれ, TypeScript本体をレポジトリから持ってこないと話になら無い.

グローバルインストールする場合, 下記でnightlyのTypeScriptを持ってくる

npm install -g typescript@1.6.0-beta

tsc --version

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

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

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

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


tsconfig.json or tscのオプション

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

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

一点注意すると, tscに渡すファイルは.tsではなく, .tsxである.

.tsだと, --jsx オプションを与えていてもJSXのパーサーは動作しない.

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


tsconfig.json

{

"compilerOptions": {
"module": "umd",
"jsx": "react"
}
}


gulp-typescriptを使う場合

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


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しておくだけ.

dtsm init

dtsm install react/react.d.ts --save


エディタ

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

JSXでも補完を有効にしたいので, Vimプラギンtsuquyomiを使うこととする.

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

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


~/.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

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


実際に作成したブツ

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

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

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

1つめがprops用, 2つ目がstate用のtypeである.


  • 本体


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>
);
}
}



  • リストの要素


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の起点)


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


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吐いてくる)

var todoItems = this.state.todoList.map(item => {

return <TodoItem key={item.key} item={item} onRemove={this.removeItem} ></TodoItem>;
});

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

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

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


終わりに

環境準備も含めて, かなりあっさりJSX + TypeScriptを用意することができた.

実際にガリガリと.tsxを書くのは1.6がリリースされてからになるだろうけど, React使いの人は今のうちからチェックしておいたらどうだろう。