ReactにするかVue.jsにするか、jQueryだけ触っていたエンジニアがサンプル作って比較してみた

今のタイミングでWebサービスを、何か新しく開発しよーって思ったら、フロントサイドをどうしようか悩みますよね?
特にVue.jsかReact.jsか... :thinking:
そんな悩んでいるあなたへの記事になります。

自分の仕事領域について
pie-chart-2.jpg

あまり普段javascript触ってません
触るとしてもjQueryが多いです
そんな人が書いた記事だと思って下さい

今回作ったもの
ReactとVue.jsで簡単なカレンダーを作成しました。

React
https://github.com/ykyk1218/react-calendar-sample

Vue.js
https://github.com/ykyk1218/vue-calendar-sample

33513722-f2d72938-d78b-11e7-898e-7d770e0bfc8f.png

コンポーネントの分け方
無題のプレゼンテーション.jpg

今回言わないこと

  • テストについて
  • ルーティングについて
  • Reduxとかvuex

環境構築

はじめに

Reactはjsx、Vue.jsは.vueという特殊な状態のファイルがあるので、それぞれシンタックスハイライトを設定できるプラグインとか入れとくと良いです。
僕はvimmerなんでvim用のプラグインいれときました。

・vue用
vim-vue

・jsx用
vim-javascript
vim-jsx

React

create-react-appというreact環境が簡単に用意できるものをfacebookが用意しているので、それを使います。

ざっくり説明

  1. npm install -g create-react-app
  2. create-react-app react-calendar-sample
  3. cd react-calendar-sample
  4. npm start

http://localhost:3000/
へアクセス

詳しくはこちら!
https://reactjs.org/blog/2016/07/22/create-apps-with-no-configuration.html

Vue.js

vue-cliというコマンドラインツールがあるので、それで作ります。

ざっくり説明
1. npm install -g vue-cli
2. vue init webpack vue-calendar-sample
3. cd vue-calendar-sample
4. npm install (時間かかる)
5. npm run dev

http://localhost:8080
へアクセス

詳しくはこちら!
https://github.com/vuejs/vue-cli

所感

どちらもコマンドで簡単にモダンな環境ができるので、楽です。
特にハマることもありませんでした。
実際のプロジェクトに組み込むとかなるとまた話は別かもしれませんが...

CSSの書き方

どちらもコンポーネント指向なので、CSSもできれば閉じた中で使いたいですよね。

React

コンポーネント内にCSSのscopeを収めるにはreact-css-moduleを使います。
create-react-appでやるには一手間必要。

ざっくり説明
1: npm run eject
2: npm install --save react-css-modules
3: config/webpack.config.dev.jsを編集

// before
{

  loader: require.resolve('css-loader'),
  options: {
    importLoaders: 1,
  },
},

// after
{
  loader: require.resolve('css-loader'),
  options: {
    importLoaders: 1,
    modules: true,
    localIdentName: "[name]__[local]___[hash:base64:5]"
  },
},

4: コンポーネントでCSSModuleを呼ぶ

import CSSModules from 'react-css-modules'
import styles from './css/Calendar.css' 自分で作ったCSS

5: エクスポート時に指定

export default CSSModules(Calendar, styles);

注意
CSSModuleの中のクラスを指定する場合はclassNameではなく、styleNameで指定します。

<div styleName='sample'>

こうすると普通のclassっぽく扱えてコンポーネント内だけに適用される閉じたscopeになります。

詳しくはこちら!(英語)
https://medium.com/nulogy/how-to-use-css-modules-with-create-react-app-9e44bec2b5c2

Vue.js

意識することはほとんどない
.vueファイルで

<style scoped>
.sample {
  font-size: 2em;
}
</style>

と言う感じでscope属性をつけるとコンポーネントだけに適用されるCSSになります。
便利。

所感

CSSはVue.jsの方がコンポーネントだけに適用させるCSSは書きやすいですね。 :smiley:
とはいえReactもそれほど手順が面倒ではないので、大きな問題ではないと思います。

react cssとかで検索すると、CSS in JSの話が出てくるのですが、あれはどうにもしっくりきません... :frowning2:

後一応の注意点としては、React x CSSModuleでは.sampleはscopeが閉じられますが、tableとかで指定するとグローバルになってしまいます。

HTMLテンプレート

キモいキモい言われるjsxは、どうなのよ

今回は拡張的なものは入れませんでした。
pugと言われるテンプレートエンジンがありまして、それはReactでもVue.jsでもどちらも使うことができるようです。

とはいえReact x pugの事例はほとんどありませんでした...

React
https://github.com/pugjs/babel-plugin-transform-react-pug

Vue.js
https://qiita.com/shuuhei/items/4852210d362d2e9022d7

React

カレンダーコンポーネントのhtmlを抜粋してます


render() {
   let weekHtml = []
   this.week.forEach((v)=>{
     weekHtml.push(<td style={styles.calendarCell}>{v}</td>)
   })

   return (
     <div styleName='calendar'>
       <h2>{this.state.year}年{this.state.month}月</h2>
       <Button fullDate={this.state.fullDate} updateCalendarDate={this.updateCalendarDate}/>
       <div className="date"></div>
       <table styleName="calendar--tbl">
         <tbody>
           <tr>
             {weekHtml}
           </tr>
           <tr>
             {this.renderBlankTable(1)}
           </tr>
           <tr>
             {this.renderBlankTable(2)}
           </tr>
           <tr>
             {this.renderBlankTable(3)}
           </tr>
           <tr>
             {this.renderBlankTable(4)}
           </tr>
           <tr>
             {this.renderBlankTable(5)}
           </tr>
           <tr>
             {this.renderBlankTable(6)}
           </tr>
         </tbody>
       </table>
     </div>
   );
}

Vue.js

<template>
  <div id="calendar">
    <p>{{ year }}年{{ month }}月</p>
    <Button :fullDate="fullDate"/>
    <table>
      <tr>
        <td v-for="w in week">{{ w }}</td>
      </tr>
      <tr>
        <DateCell v-for="(w, i) in week" :viewDate="viewDate(i, 1)" />
      </tr>
      <tr>
        <DateCell v-for="(w, i) in week" :viewDate="viewDate(i, 2)" />
      </tr>
      <tr>
        <DateCell v-for="(w, i) in week" :viewDate="viewDate(i, 3)" />
      </tr>
      <tr>
        <DateCell v-for="(w, i) in week" :viewDate="viewDate(i, 4)" />
      </tr>
      <tr>
        <DateCell v-for="(w, i) in week" :viewDate="viewDate(i, 5)" />
      </tr>
      <tr>
        <DateCell v-for="(w, i) in week" :viewDate="viewDate(i, 6)" />
      </tr>
    </table>
  </div>
</template>

所感

Vue.jsの方がtemplate部分が綺麗に切り出せているので、書いていて気持ちがいい。Reactは普通にjavascriptでループ処理を書いてあげないといけないので、ロジックが入ってくるとどうしてもごちゃっとしてしまう気がします。

Reactはこうですが...

let weekHtml = []
this.week.forEach((v)=>{
  weekHtml.push(<td style={styles.calendarCell}>{v}</td>)
})

Vue.jsはこう

<td v-for="w in week">{{ w }}</td>

とはいえ...
Vue.jsは綺麗に書ける反面、処理が複雑になってくると逆に辛くなりそうです。
Reactの方はなんか、なんでもできそうな感じがあります。

コンポーネント間の値受け渡し

ボタンコンポーネントでボタンをクリックした時に表示をどうやって切り替えているのかを見ていきましょう。

React

親→子
コンポーネント呼び出し時に、属性として渡したい値をkey=valの形で渡してあげます。

子→親
親のメソッドをpropsに渡して、callback的に子コンポーネントから呼び出すことで親に値を渡せます。

Reactのコンポーネントの値受け渡しは、こちらの記事がイメージつけやすかったです。
https://qiita.com/kyoshidajp/items/0ddb156d96bb6337f623

Calendar.js
カレンダーコンポーネントからボタンコンポーネントを呼び出す


<Button fullDate={this.state.fullDate} updateCalendarDate={this.updateCalendarDate}/>

Button.js
ボタンコンポーネント側でpropsで渡ってきた、親のメソッドを呼ぶ

  handleChangeCalendarPage = (event) => {
    const page = event.currentTarget.getAttribute("data-page")
    const fullDate = this.props.fullDate
    const tmpMonth = fullDate.getMonth() + 1 + parseInt(page)

    fullDate.setMonth(tmpMonth-1)

    const year = fullDate.getFullYear()
    const month =fullDate.getMonth()+1

    //call parent component method
    this.props.updateCalendarDate(year, month)
  }


  return(
    <div styleName="button-wrapper">
      <button styleName="button" style={styles.buttonLeft} onClick={this.handleChangeCalendarPage} data-page="-1">&lt;</button>
      <button styleName="button" style={styles.buttonRight} onClick={this.handleChangeCalendarPage} data-page="1">&gt;</button>
    </div>
  )

Vue.js

親→子
親から子供への値の渡し方はReactとほぼ一緒です。
コンポーネントを定義時に属性として値を渡してあげます。

子→親
this.$parent.hogehogeで親コンポーネントの値にアクセスできます

$parentで親の値を見に行くのはアンチパターンのようでした。 :bow:
https://twitter.com/kazu_pon/status/789644939260928000

Calendar.vue


<Button :fullDate="fullDate"/>

Button.vue

<template>
  <div class="button-wrapper">
    <button class="button-left" v-on:click="handleCalendar('-1', fullDate)">&lt;</button>
    <button class="button-right" v-on:click="handleCalendar('1', fullDate)">&gt;</button>
  </div>
</template>
<script>
  export default {
    name: 'Button',
    props: ['fullDate'],
    methods: {
      handleCalendar: function(page, fullDate) {
        const tmpMonth = fullDate.getMonth() + 1 + parseInt(page)
        fullDate.setMonth(tmpMonth-1)
        this.$parent.year  = fullDate.getFullYear()
        this.$parent.month = fullDate.getMonth()+1
        this.$parent.date = 0
      }
    }
  }
</script>

この辺で親コンポーネントのプロパティを直接見にいって値を変更してます。
Reactではこういうのできなさそうでした。
(探し方悪いだけかも...)

this.$parent.year  = fullDate.getFullYear()
this.$parent.month = fullDate.getMonth()+1
this.$parent.date = 0

所感

ほぼ一緒。
子コンポーネントから親コンポーネントの値を変更するときに、Vueでは直接親の値を見にいけたので、一手間楽でした。
(でもこういうのはあんまりやらない方がいいのか...この辺の感覚知はまだない...)

ハマったところ

知識不足が多分にあるところですが、一応

React

最後にexportし忘れていて、なんかimportできない!?なんで?
エラーメッセージわかりづらいのですよ...
(そもそもexportさせないといけない、みたいな概念がなかったけど...)

イベントハンドリングで引数渡すやり方
ハマったってほどでもなかったけど、なんかうまいやり方ないかなーって探し求めて、data属性に値として渡すようにしました。

<button styleName="button" style={styles.buttonLeft} onClick={this.handleChangeCalendarPage} data-page="-1">&lt;</button>
<button styleName="button" style={styles.buttonRight} onClick={this.handleChangeCalendarPage} data-page="1">&gt;</button>

Vue.js

メソッド内でdataの値を更新して、無限ループに陥る

そりゃそーだろっていう話ですが :sweat_smile:
カレンダーコンポーネント内でループさせるたびにdateの値を1足していきたかったので下記のようにさせました


data: function() {
  return {
    date: 1,
    week: ["日","月","火","水","木","金","土"],
   year: year,
    month: month,
    viewDate: function() {

      //============
      //色々な処理
      //============

      this.date += 1
      return this.date
    }
  }
}

dateが更新されたタイミングでviewDate関数がまた呼ばれてしまうので無限ループに陥ってしまいます。親切なことにVue.jsはブラウザが固まらないように、ループ処理を途中で打ち切ってくれます。

一応consoleにはエラー出ていたのでですが、なんでかなーと悩んでいました。

arrow関数で定義をした場合とfunction()で定義した場合にthisの値が異なる

まだ理由がはっきりしていないのですが、arrow関数でthisを呼ぶとthisの値が期待したものでなくなります。
なんかノリで「やっぱ今はarrow関数っしょ」とかやるとundefinedが帰ってきて痛い目を見ます。

こちらに参考にして見てください。
Vue.jsに書いてある「アロー関数は、this が期待する Vue インスタンスではなく・・・」とは? - Qiita

所感

全体通してVue.jsの方がハマった時間が多かったです。

わりかし最初に覚えておかないといけない記法とかがあるので、そこがスッと出てくるともっとスムーズにいったかなという印象でした。

全体まとめ

今のところの結論としては

  • シンプルな処理はVue.jsの方が綺麗にかけて気持ちがいい
  • 複雑なことやろうと思うとReactの方が柔軟にできそう
  • Vueの方がハマる時間が多そう

というところでした。

後Vue.jsは検索すると、日本語の公式ドキュメントが上位にきてくれるので助かります。
Reactは公式ドキュメントを日本語に翻訳をしてくれている方が何人かいますが、ボランティア?でやっているので、メンテナンス性は微妙かも(ちゃんと読めてないのでわからないです)

React→Vue.jsに移行した人の記事はいくつかあるのですが、みんなReactに疲れた人が書いているのかVue.jsよりの記事が多いです。

ありがちな結論ではなりますが

  • シンプルなWebアプリ→Vue.jp
  • 複雑なWebアプリ→React

というのが今のところだとよさそうですね。

さてどっちを採用しようかな...

おまけ:数値からみる比較

Googleトレンド

スクリーンショット 2017-12-03 23.04.34.png

青い線がVue.js、赤い線がReact
Vue.jsの方がトレンドとしてはキテます。

Twitterフォロワー数

vue.js: 約5万3千
https://twitter.com/vuejs

react: 約19万
https://twitter.com/reactjs

圧倒的React

Qiita投稿数

vue.js 713件
React:1290件

雰囲気そのうちVue.jsが抜きそうです。