LoginSignup
41
22

More than 5 years have passed since last update.

Riot を React の文脈で理解する

Last updated at Posted at 2017-03-04

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-mounton させてコールバックを渡せば 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 では mounton します。

ReactDOM.render()

html
<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>

updateon することでアップデート直前に値の再計算など、なんらかの処理を実行できます。

componentDidUpdate()

<ex>
    <script>
        this.on( 'updated', () => {
            // right after the tag template is updated after an update call
        } )
    </script>
</ex>

アップデート直後になにか実行したいなら updatedon します。

順序

アップデートに関するライフライクルも 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-unmounton してアンマウント直前に何かを実行できます。

<ex>
    <script>
        this.on( 'unmount', () => {
            // when the tag is removed from the page
        } )
    </script>
</ex>

unmounton するとアンマウント直後に実行できます。

その他

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 のドキュメントを解説することになってしまいます。

つまり、ここには書かないでいいですね?:thinking:

公式サイトのガイド を見てもらうのが一番わかりやすいでしょう。

...とはいえ、簡単なのでよくある文法だけ書いておきますね :thumbsup:

ループ

<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 と同じように onclickonchange にバインドしたい関数を渡します。


こんなところですね。

あとスタイルの記述も比較したいところではありますが、ちょっと疲れてしまったので一旦ここで公開します。

また、 誤っている箇所がありましたら編集リクエストかコメントで教えてください :bow:

よろしくお願いします。

41
22
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
41
22