はじめに
前回、『スクローラブルリスト』を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
のメリットについても少し深く考察してみたいと思います。
長々書きましたが、最後まで読んでいただきありがとうございます。皆様の何かお役に立てたなら光栄です。