Riot は React のような UI ライブラリです。
ライブラリの特徴については 公式サイトの比較 や 以前に書いた Qiita を参照してもらえれば伝わるかと思います。
React が DOM の複雑な操作をあくまでも JavaScript で解決しようとしているライブラリに対して、Riot はそこを HTML+α で解決するため Web Components を先取りして書けるようにしたライブラリを指向しているという点で大きな思想の違いは存在しています。
実際、Riot の issue では構文を Web Components 準拠にする変更があがったり、Shadow DOM 実装が検討されていたりと、割りと HTML の世界に近いライブラリとなっています。
その上で、ここではコードベースで、React でこう書くとき Riot はどう書くの? という視点で比較してみます。
React の API 名に続いて Riot で書く場合のコードを記しました。
前提
比較対象のバージョンは次のとおりです。
- Riot v3.4.2
- React v15.5.0
Riot は v4 で内部実装の大幅な変更のほか、新たな DOM アップデート戦略への転換などのため API の変更が検討されています。 Riot #2283
React も v16 で Fiber への変更や API の変更が検討されています。 React #8854
2017~2018 年は両ライブラリとも期待度の高いアップデートが控えていますね。
Riot のプリプロセッサ
Riot は独自のコンパイラによってコンポーネントを js に変換します。このときコンポーネントの Script, HTML, CSS すべてにおいて好みのプリプロセッサを使用できます。
ここで説明するのはあくまで Riot のデフォルトの仕様です。
Riot の気に入った部分だけ使いつつ、Script を好みの言語や仕様( 自作もできます! )にするということもできます。プリプロセッサについては 公式サイトのガイド を参照ください。
ライフサイクル
コンポーネントをマウントしてアップデート、アンマウントするなどのライフライクルを比較します。
マウント
constructor()
<ex>
<p>Hello, { userName }</p>
<script>
this.userName = 'John'
</script>
</ex>
Riot のコンポーネントはあくまでも 普通の HTML のように記述するので、Class
を意識しません。そのため constructor()
で定義しておきたいことは、ただ そこ に書くだけです。
componentWillMount()
<ex>
<script>
this.on( 'before-mount', () => {
// before the tag is mounted
} )
</script>
</ex>
初回のレンダリング前に実行させたい内容はコンポーネントに before-mount
を on
させてコールバックを渡せば OK です。
render()
<ex>
<p>Hello</p>
</ex>
もはや API はありません。普通の HTML のように書いてください。
componentDidMount()
<ex>
<script>
this.on( 'mount', () => {
// right after the tag is mounted on the page
} )
</script>
</ex>
componentWillMount()
と同様の手法で、Riot では mount
を on
します。
ReactDOM.render()
<ex></ex>
<script>
riot.mount( 'ex' )
</script>
HTML にコンポーネントをマウントするときは riot.mount()
にコンポーネント名を渡すだけです。
順序
以上のライフライクルは React と同じ順序で実行されます。
つまり、以下のようなコンポーネントを実行すると:
<ex>
<p>{ console.log( 'render' ) }</p>
<script>
console.log( 'constructor' )
this.on( 'before-mount', () => console.log( 'before-mount' ) )
this.on( 'mount', () => console.log( 'mount' ) )
</script>
</ex>
コンソールには以下の順序で出力されます。
constructor
before-mount
render
mount
console.log( 'constructor' )
がスクリプトの最終行にあっても変わりません。
アップデート
componentWillReceiveProps()
Riot にはこれに相当するものはありません。
shouldComponentUpdate()
<ex>
<script>
shouldUpdate ( data, nextOpts ) {
return nextOpts.message !== 'goodbye'
}
</script>
</ex>
Riot で不要なアップデートを避けるには shouldUpdate()
を使います。
data
にはthis.update()
で更新された新しい値が渡され、nextOpts
には新しい opts
( React でいうところの props
)が渡されます。
Riot には this.state
という予約済みのプロパティがありません。this.opts
にマウント時のオプションが渡ってくること以外は、開発者の裁量に委ねられています。
componentWillUpdate()
<ex>
<script>
this.on( 'update', () => {
// allows recalculation of context data before the update
} )
</script>
</ex>
update
を on
することでアップデート直前に値の再計算など、なんらかの処理を実行できます。
componentDidUpdate()
<ex>
<script>
this.on( 'updated', () => {
// right after the tag template is updated after an update call
} )
</script>
</ex>
アップデート直後になにか実行したいなら updated
を on
します。
順序
アップデートに関するライフライクルも React と同じ順序で呼び出されます。
つまり、以下のようなコンポーネントを実行すると:
<ex>
<p>{ console.log( 'render' ) }</p>
<script>
shouldUpdate ( data, nextOpts ) {
console.log( 'shouldUpdate' )
return true
}
this.on( 'update', () => console.log( 'update' ) )
this.on( 'updated', () => console.log( 'updated' ) )
setTimeout( () => this.update(), 10 );
</script>
</ex>
コンソールには以下の順序で出力されます。
render # マウントによる出力
shouldUpdate
update
render
updated
アンマウント
componentWillUnmount()
<ex>
<script>
this.on( 'before-unmount', () => {
// before the tag is removed
} )
</script>
</ex>
before-unmount
を on
してアンマウント直前に何かを実行できます。
<ex>
<script>
this.on( 'unmount', () => {
// when the tag is removed from the page
} )
</script>
</ex>
unmount
を on
するとアンマウント直後に実行できます。
その他
this.setState()
<ex>
<script>
this.update( {
userName: 'Michael'
} )
</script>
</ex>
例えば this.userName
という変数があるとき、this.update()
に同じキーを持つオブジェクトを渡すことで状態の更新と再レンダリングができます。
以下のように書いても同じですが、これだと shouldUpdate
で値を受け取れないのであまり使わないほうが良いでしょう。
this.userName = 'Michael'
this.update()
forceUpdate()
Riot にはこれに相当するものはありません。shouldUpdate
で制御します。
defaultProps
Riot にはこれに相当するものはありません。
規定値を設定する場合は、普通の JavaScript で:
this.userName = opts.userName || 'name'
のようにすれば OK です。
HTML
HTML の文法は React が JSX なのに対して Riot はほぼ標準の HTML に近いスタイルで記述できます。
これは比較よりもむしろ Riot のドキュメントを解説することになってしまいます。
つまり、ここには書かないでいいですね?
公式サイトのガイド を見てもらうのが一番わかりやすいでしょう。
...とはいえ、簡単なのでよくある文法だけ書いておきますね
ループ
<ex>
<ul>
<li each="{ person in persons }">{ person }</li>
</ul>
<script>
this.persons = [ 'John', 'Michael', 'David' ]
</script>
</ex>
繰り返したい要素やコンポーネント自体に each
を書くことで、その要素が繰り返されます。
条件
<ex>
<p if="{ enabled }">Hello</p>
<script>
this.enabled = true
</script>
</ex>
条件が真であるときだけマウントさせたい要素やコンポーネントに if=XXX
の形で記述すれば OK です。
{}
の中は普通の JavaScript なので、こんなふうに:
<ex>
<ul>
<li each="{ person in persons }" if="{ person.length > 4 }">{ person }</li>
</ul>
<script>
this.persons = [ 'John', 'Michael', 'David' ]
</script>
</ex>
使うこともできますし、関数で評価することもできます。
イベント
<ex>
<button type="button" onclick="{ click }"></button>
<script>
click ( e ) {
// execute something after being clicked
}
</script>
</ex>
React と同じように onclick
や onchange
にバインドしたい関数を渡します。
こんなところですね。
あとスタイルの記述も比較したいところではありますが、ちょっと疲れてしまったので一旦ここで公開します。
また、 誤っている箇所がありましたら編集リクエストかコメントで教えてください
よろしくお願いします。