前回の続きで、React Tutorialのサンプルコードを今度はLiveScriptで書き直してみました。やり方は前回と一緒なので、まだ見てない人はそっちも見てください。
index.htmlを書き換える
pubilc/index.html
を次のように書き換えます。
<!DOCTYPE html>
<html>
<head>
<title>Hello React</title>
<!-- Not present in the tutorial. Just for basic styling. -->
<link rel="stylesheet" href="css/base.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/0.3.2/marked.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/livescript/1.4.0/livescript-min.js"></script>
<script src="http://www.preludels.com/prelude-browser-min.js"></script>
</head>
<body>
<div id="content"></div>
<script type="text/ls" src="scripts/example.ls"></script>
<script>
require("LiveScript").go();
</script>
</body>
</html>
今回はlivescript-min.js
とprelude-browser-min.js
が加わっています。また、MIMEタイプはtext/ls
となっています。最後のスクリプトコードはLiveScriptをJavaScriptに動的にコンパイルするのに必要です。
example.jsをexample.lsに置き換える
scripts/example.js
を下記のscripts/example.ls
に置き換えます。コードでやってることは(たぶん)全く一緒です。
/**
* This file provided by Facebook is for non-commercial testing and evaluation purposes only.
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
require! 'prelude-ls': {Obj}
class Component extends React.Component implements React.DOM
@element = ->
React.create-factory(@) ...
class Comment extends Component
render: ->
@div class-name: \comment,
@h2 class-name: \commentAuthor,
@props.author
@span dangerouslySetInnerHTML:
__html: marked @props.children.to-string!, sanitize: true
class CommentBox extends Component
(props) !->
super ...
@state = data: []
access-comments: (settings) !->
default-settings =
url: @props.url
data-type: \json
success: (data) !~>
@set-state data: data
error: (xhr, status, err) !~>
console.error @props.url, status, err.to-string!
$.ajax default-settings with settings
load-comments-from-server: !~>
@access-comments cache: false
handle-comment-submit: (comment) !~>
@set-state data: @state.data ++ [comment], !->
@access-comments type: \POST, data: comment
component-did-mount: !->
@load-comments-from-server!
set-interval @load-comments-from-server, @props.poll-interval
render: ->
@div class-name: 'commentBox',
@h1 null, 'Comments'
CommentList.element data: @state.data
CommentForm.element on-comment-submit: @handle-comment-submit
class CommentList extends Component
render: ->
@div class-name: \commentList,
@props.data.map (comment, index) ->
Comment.element author: comment.author, key: index,
comment.text
class CommentForm extends Component
handle-submit: (e) !~>
e.prevent-default!
ref-nodes = @refs{author, text} |> Obj.map React.findDOMNode
ref-vals = ref-nodes |> Obj.map (.value.trim!) |> Obj.compact
if \author of ref-vals and \text of ref-vals
@props.on-comment-submit ref-vals
ref-nodes |> Obj.each -> it.value = ''
render: ->
@form class-name: \commentForm, on-submit: @handle-submit,
@input type: \text, placeholder: 'Your name', ref: \author
@input type: \text, placeholder: 'Say something...', ref: \text
@input type: \submit, value: \Post
React.render CommentBox.element(url: 'comments.json', poll-interval: 2000),
document.get-element-by-id \content
もう、なんとういうか、なにか別世界に来た感じです。さて、解説していきたいと思います。なお、classベースなどのcoffee-reactと被る部分は説明を省きます。
<タグ>からの解放
JSXおよびJSX派生言語(coffee-react等)で一番の特徴はHTMLと同じタグ表記です。LiveScriptにもreact-livescript-jsxというものがあるのですが、今回はつかっていません。SGMLからの伝統を引き継ぐこのタグ表記はHTMLだけしか知らない人にはいいかもしれませんが、いかんせん、冗長すぎて書くのがつらいです。HAML/Jade/Slimとかに慣れている人からすると、今更タグで書くのは苦痛です。
React.jsはJSXを使いたくない人にもちゃんとコーディングする方法を用意しています。新たにelementを作る方法は三つです。
-
React.createElement()
を使う。 -
Recat.createFactory()
を使う。 -
React.DOM.要素名()
を使う。(HTML要素のみ)
最後はHTML要素のみにしか使えませんが、どれを使ってもいいです。ただ、どれも書き方が冗長です。何度も何度もReact...
って書いていくのはつらいです。そこで、一工夫することにします。
HTML要素はReact.DOM
をmixin!
例えば、div要素はReact.DOM.div()
とすることで作ることが可能です。では、これをもっと簡単にスマートにするには?よくみるとdiv()
はReact.DOM
のクラスメソッドです。そう、だったらmixinしちゃえば、@div()
とかけるようになるということです。LiveScriptではclass定義にimplements
キーワードでmixinが可能です。親クラスとは別に指定できますので、これを使いましょう。
ReactClassはRecat.createFactory()
をクラスメソッドで実行!
残念ながらReactClassにはReact.DOM
は使えません。同じ方法を取ろうにも、class定義時にあとどれだけReactClassを定義するかなんて知る方法はありません。同じ方法はあきらめて、簡単にFactoryを取得・実行するようにすればいいと考えました。各ReactClassでelemente()
クラスメソッドを実装し、内部ではReact.createFactory()
でFactoryの生成、そして、渡された引数を渡して実行すればいいのです。
まとめたものをクラスとして作って継承していく
こうして、上をまとめた最低限のクラスは下記になります。
class Component extends React.Component implements React.DOM
@element = ->
React.create-factory(@) ...
コンポーネント作成するときは、React.Component
の代わりに上のCompoent
を継承するようにすると、render()
で@div()
やComment.element()
という表記ができるようになります。最終的には今回のコードのようなHAML/Jade/Slimっぽいコードで書くことができると言うことです。
なお、上の3行の部分はパブリックドメインですので、お使いになる場合はご自由に。
その他
キャメルケースをチェインケースにしたり、パイプ|>
で繋いだり、prelude.lsを使ったり、LiveScriptっぽくできそうな所はなるべくしてみて、DRYの原則に立つようにしてみました。でも、そもそものコードが短いので、あまり関数型プログラミングっぽくできなかったです。
まとめ
いかがだったでしょうか?LiveScriptはCoffeeScriptをさらに強力にしたCocoに関数型プログラミングの要素を追加した、もう、なんでもありな言語に仕上がっていますので、すごくすっきりとしたコーディングが(たぶん)可能です。これを機に皆さんもLiveScriptを触ってみてください。次回はFlux Tutorial - Todo Listあたりに挑戦してみようかなと思っています。