ReactチュートリアルをCoffeeScriptで書こうとして四苦八苦、試行錯誤したしている記録
開発環境
まずは、ここから、Reactチュートリアルをクローンして、 react-tutorial ディレクトリに入ります。
$ git clone https://github.com/reactjs/react-tutorial.git
$ cd react-tutorial
そして、必要なパッケージをインストールします。
$ npm i --save coffee-react
$ npm i --save node-dev
node.jsと、npmは最新版にしておいてくださいね。
あと、 ./node_modules/.bin にPATHを通しておくと便利です(というか通ってないと動きません)。
デバッグ用のnodeモジュールも入れておきました(後で使います)。
そして、別ターミナルでコンパイル監視とアプリケーションサーバーを起動します。
$ cjsx -wc public/scripts
監視モードに入ると、そのコンソールが使えなくなるので、別コンソールで、
$ node-dev server.js
さらに別のコンソールで react-tutorial/public/index.html を開き、サンプルの削除と、スクリプトの種類を変更します。
<script type="text/babel" src="scripts/example.js"></script>
<script type="text/babel">
の、 example.js の行を削除(いきなりチュートリアルをやるため)。
そして、その下のscript行を、
<script type="text/cjsx">
と修正。
JSXのCoffeeScript版で記述することを高らかに宣言します。
本来であれば、これで、 script タグの間にCJSXをゴリゴリ書いていくとReactアプリケーションが作れます。
…と言いたいところですが、いろいろハマリどころが多いので書いていきます。
ReactをCoffeeScriptで書くということ
そもそもReactはJSXというシンタックスシュガーを用いて記述しますが、それをこれまたシンタックスシュガーであるCoffeeScriptで書こうというのがそもそも間違いの始まり。
Reactチュートリアルの、
var CommentBox = React.createClass({
render: function() {
return {
<div className="commentBox">
Hello World!.
</div>
};
}
});
ReacDOM.render(
<CommentBox />,
document.getElementById('content')
);
これを、CoffeeScript(正確にはCJSX)に直すと下記のようになる。
CommentBox = React.createClass
render: ->
return <div className="commentBox">
Hello World.
</div>
ReactDOM.render <CommentBox />,
document.getElementById("content")
ここに辿り着くまでにかなり長い旅をした(遠い目)。
これのハマリポイントとしては、まずはインデント。
return
<div className="commentBox">
Hello World.
</div>
普通にインデントでこう書くとコンパイラに怒られる。なんでやねん。
return (
<div className="commentBox">
Hello World.
</div>
)
しかし、こうするとコンパイルは通ってちゃんと動く。
しかしCoffeeScript的に美しくない。
なので、最初の正解例のように、
return <div className="commentBox">
Hello World.
</div>
こうする。
これで、ちゃんとコンパイルも通って実行出来る。
return で始まって div で終わってるのでちょっと気持ち悪いが、Reactはひとつのコンポーネントでは親要素をひとつしか返せないのでこれでヨシとする。
人様のチュートリアルをやってみる
こういう時は自称「初心者」の方のblogが役にたつ。ポエムだとただのメモなので環境などが推測しづらい。というわけでこの方のこれをやってみる。
これをCJSXで書いてみる。
まずは、これ
const TodoList = () => {
return (
<ul>
<li>ほげ</li>
<li>ほげ</li>
<li>ほげ</li>
</ul>
);
}
ReactDOM.render(
<TodoList />,
document.getElementById('content')
);
これを、
TodoList = =>
return <ul>
<li>ほげ</li>
<li>ほげ</li>
<li>ほげ</li>
</ul>
ReactDOM.render <TodoList />,
document.getElementById('content')
こう書いた。ハイ、察しのいい方は気づきましたね。ブラウザになにも出てこないんですね。バベってみたり、CoffeeScript2でコンパイルしてからバベってみたり、いろいろしたんですけどダメなんです。
そうなんです、インデント間違ってました。
これでは、最初の render が実行されません。
というわけで、
TodoList = =>
return <ul>
<li>ほげ</li>
<li>ほげ</li>
<li>ほげ</li>
</ul>
ReactDOM.render <TodoList />,
document.getElementById('content')
こうしたらちゃんと動きました。
初期生成されるコンポーネントを指定する
なんか、最初にレンダリングするところで初期コンポーネントを指定するのが汎用性が低く感じられて(←汎用厨)変数で指定するようにしてみた。
#============================================================================
# Todo List
#============================================================================
TodoList = =>
return <ul>
<li>ほげ</li>
<li>ほげ</li>
<li>ほげ</li>
</ul>
#============================================================================
# Initial Component
#============================================================================
initComponent = TodoList
#============================================================================
# Display Base Component
#============================================================================
MainComponent = =>
return React.createElement(initComponent, null)
ReactDOM.render <MainComponent />,
document.getElementById('content')
いちおうコンパイルは通って動いている。「初期コンポーネントを変数に入れるのと、レンダリングするコンポーネントを指定するのは、同じ手間じゃね?」というツッコミはスルー。
………としたんですが、この後に TodoList に引数を渡さないといけなくなり、結局こうなりました。
#============================================================================
# Todo List
#============================================================================
TodoList = =>
return <ul>
<li>ほげ</li>
<li>ほげ</li>
<li>ほげ</li>
</ul>
#============================================================================
# Display Base Component
#============================================================================
ReactDOM.render <TodoList />,
document.getElementById('content')
元に戻りました……。
クラスを作ってみる
CoffeeScriptや、ES6の特徴である「クラス」が作れるので、クラスを作ってみます(元のチュートリアルで作ってるのでやってるだけ)。先頭に、TodoApp というクラスを定義します。コンストラクターで表示するメンバーを定義します。
#============================================================================
# TodoApp
#============================================================================
class TodoApp extends React.Component
constructor:->
super()
@state = {items: ['ほげ', 'ほげ', 'ほげ']}
そして、render メソッドを定義します。
render:->
return <div>
<h3>Todoアプリ</h3>
<form>
<input />
<button>Add</button>
</form>
<TodoList items={@state.items} />
</div>
フォームの下で、コンストラクターで定義した配列を TodoList に渡しています。
次に、TodoList を、渡された配列を表示するように改修します。
class TodoList extends React.Component
constructor:(props)->
super(props)
@state = props
render:->
return <ul>
{@state.items.map (item, index) ->
<li key={index}>{item}</li>
}
</ul>
TodoApp で TodoList を呼び出した際に、items というパラメータで渡している配列を、constructor で props という変数で受け取っています。super() を呼ぶのは決まりになってますね。そして、mutableな変数 state に代入しています(stateについて分からない方はReactをググってください)。
renderメソッドではconstructorで受け取った配列をmapでループ処理しています。@state.itemsの各要素を(item, index)という二つの配列で、要素と配列番号を取り出します。それをそのまま**
全部まとめるとこんな感じですね。
#============================================================================
# TodoApp
#============================================================================
class TodoApp extends React.Component
constructor:->
super()
@state = {items: ['ほげ', 'ほげ', 'ほげ']}
render:->
return <div>
<h3>Todoアプリ</h3>
<form>
<input />
<button>Add</button>
</form>
<TodoList items={@state.items} />
</div>
#============================================================================
# Todo List
#============================================================================
class TodoList extends React.Component
constructor:(props)->
super(props)
@state = props
render:->
return <ul>
{@state.items.map (item, index) ->
<li key={index}>{item}</li>
}
</ul>
#============================================================================
# Display Base Component
#============================================================================
ReactDOM.render <TodoApp />,
document.getElementById('content')
多分動くと思います。
次は、チュートリアルにもあるように、配列を空にしてユーザーの入力されたものを追加して、それをリアルタイムで表示する、という感じに改修してみたいと思います。
#永遠につづく。