
はじめに
前回、『スクローラブルリスト』をReact でやってみました。
今回は、それを通して分かった Vue と React の違いについて、筆者なりに解説してみたいと思います。
2つの疑問
以前、Vueでやってみた『スクローラブルリスト』と題材自体は同じなんですが、Vueの感覚のまま、いざ、React でやってみようとした途端に手が止まり、思考が行き詰ってしまいました。
その原因は次の二つの大きな疑問にぶち当たったからです。
HTMLの中の繰り返し構造や条件別の構造はどうやって実現する?
イベントハンドラ呼び出し時の
undefinedエラー
Vue では、前者はディレクティブを駆使することで実現していて、後者は特に意識する必要がありませんでした。
ですが、React ではこの辺りが違っていたのです。
Vue のディレクティブって React ではどうする?
Vueのコンポーネントファイルでは<template>と<script>そして<style>の三つのトップレベルの言語ブロックを定義します。実は、この三つのブロックが構造、振る舞い、スタイルを分離させ、見通しをよくしていると理解しています。
この考えを基本にReactのコードを見てみると、まず「構造と振る舞いが分離されてなぁ~っい」っと戸惑ったのとv-for や v-if ってなディレクティブを使った構造の実現というのはReactでどうやるの?っという疑問がわいてきました。
実は、ここらへんのアプローチがVueとReactで少し違っていて
Vueは構造を描いてそこに論理を埋め込むというアプローチなのですが、React はUIをコードで書くというアプローチなのです。
React にはマークアップとそれを生成するコードが同居するという中心概念が存在し、テンプレートと表示ロジックの分離よりもコンポーネント単位の分離を優先します。
これが Vue と React の考え方が大きく違う点と言えるでしょう。
配列の要素数分の<div></div> セクションを定義するという例を React で書くとこんな感じでしょうか
render() { return( <div className="xxx"> { this.state.columns.map((name,col) => { const className = this.state.sortKey === name ? "active " + name : name const arrow = this.state.sortKey === name ? (this.state.sortOrders[name] > 0? <span className={"arrow asc"}></span> : <span className="arrow dsc"></span>) : ""; return ( <div className={className} key={col} onClick={this.sortBy.bind(this, name)}> {name} {arrow} </div> ) }) } </div>
5行目でcolumns配列の要素数分繰り返しを宣言し、6行目~11行目で 変数classNameとarrowの値を算出して、それらを使って12行目以降で<div>...</div>セクションのHTMLを生成しています。
このようにReactでは、UIをコードで書くのです。
今回のスクローラブルリストですと、「列ごとの繰り返し」と「行ごとの繰り返し」があったり、「奇数行、偶数行の判断」といった論理があるわけですが、
Vueではこれを<template> 内の v-for や v-if などというディレクティブを使って構造を定義していきます。
一方、React では、JSX (JavaScript XML)というマークアップ言語を記述するためのXML風シンタックスを使い、繰り返しや条件分岐にあたる表示ロジックを言葉で説明すような感覚でコードを記述していくのです。
言い換えると
Vueでは全体の構造をイメージしながら、それをtemplateブロックに書いていき、「あっ、この部分は行ごとの繰り返し」とか「この部分は奇数行、偶数行でスタイル切替」ってな具合に設計と実装を進めるんですが、
Reactでは、まず頭の中で全体の構造と繰り返しや条件分岐が要る箇所を描いて、その論理単位をもとにコードを実装していくって流れです。
こう考えると
Reactではページ全体の構造を描く部分はフレームワークの外で、繰り返しや条件を表す論理単位にコンポーネントを定義することになるので、その粒度は自ずと小さくなる傾向で、HTMLとそれを生成するコードが同居するため構造と振る舞いの分離があいまいにりがちです。このためVueに比べ全体が見通しにくいという印象です。(この辺りは賛否両論、好みの問題といったところかもしれませんが)
イベントハンドラの呼び出しコンテキストは?
もう一つ、Reactではどうするの?となったのがイベントハンドラの呼び出しコンテキスト、すなわちthis参照の扱い方です。
- Vueの例
Vueでは、<template>の中でv-onディレクティブを使って v-on:click=handleClickEvent ってな具合にイベントハンドラを登録します。
このとき、handleClickEvent は現在のView(すなわちイベントハンドラが記述されている <template> ブロック)のみを扱う ViewModel(すなわち <script> ブロック)にのみバインドされるよう制限されます。
なので、this 参照のバインドは特に意識しなくとも、コンポーネント内に閉じていますし、パラメタも v-on:click=handleClickEvent(param) っといった感じで渡せます。
<template>
…中略...
<div v-for="(val, idx) in columns"
v-bind:key=idx
v-on:click="sortBy(val)"> <==== イベントハンドラの呼び出し
{{ val }}
</div>
…中略...
</template>
<script>
…中略...
method: {
sortBy(val) { ... } <==== イベントハンドラの定義
…中略...
}
</script>
こんな具合にとっても素直に実装できます。
ところが、Reactになると少々事情が異なります。
- Reactの例
イベントハンドラの呼び出しは render 関数内に記述し、定義は React のクラスメソッドとして記述しますが、Vue のようにデフォルトでバインドされるわけではありません。
render 関数内のイベントハンドラの呼び出しで React クラスに明示的にいちいちバインドしないといけません。そうしないとイベントハンドラの呼び出しで Undefined が出ます。(もしくはコンストラクタで bind する方法もあります)
こんな具合です。
import React, { Component } from 'react'; export class MemberList extends Component { constructor(props) { ... } sortBy(name) { ... } <==== イベントハンドラの定義 render( return ( … onClick={this.sortBy.bind(this, name)}> <==== イベントハンドラの呼び出し … ) ) }
もしくはアロー関数を使って
import React, { Component } from 'react'; export class MemberList extends Component { constructor(props) { ... } sortBy = (name) => { ... } <==== イベントハンドラの定義 render( return ( … onClick={() => this.sortBy(name)}> <==== イベントハンドラの呼び出し … ) ) …
っという具合に書く必要があります。
Reactではこの点を留意しとかないと、実行時にUndefinedのランタイムエラーが出て???ってなっちゃいます。
まとめ
改めて React に挑戦ということで Vue でやってみたことを React でトレースしてみました。
いろんな書籍や記事をみると Vue は比較的緩いが、React はとにかく厳格であるといわれているのが理解できた気がします。
個人的には全体を俯瞰しながら設計、実装を進められる Vue の方が効率的な印象ですが、より規模の大きな開発で大勢での開発になった際にはReactの厳格さが効果を発揮するのかもしれません。
謝辞
お蔭様でスクローラブルリストを つくることで React を少し経験できました。
大きなポイントがつかめたので、他のサンプルでも試しつつ Reactのメリットについても少し深く考察してみたいと思います。
長々書きましたが、最後まで読んでいただきありがとうございます。皆様の何かお役に立てたなら光栄です。