今のタイミングでWebサービスを、何か新しく開発しよーって思ったら、フロントサイドをどうしようか悩みますよね?
特にVue.jsかReact.jsか...
そんな悩んでいるあなたへの記事になります。
あまり普段javascript触ってません
触るとしてもjQueryが多いです
そんな人が書いた記事だと思って下さい
今回作ったもの
ReactとVue.jsで簡単なカレンダーを作成しました。
React
https://github.com/ykyk1218/react-calendar-sample
Vue.js
https://github.com/ykyk1218/vue-calendar-sample
今回言わないこと
- テストについて
- ルーティングについて
- Reduxとかvuex
環境構築
はじめに
Reactはjsx
、Vue.jsは.vue
という特殊な状態のファイルがあるので、それぞれシンタックスハイライトを設定できるプラグインとか入れとくと良いです。
僕はvimmerなんでvim用のプラグインいれときました。
・vue用
vim-vue
・jsx用
vim-javascript
vim-jsx
React
create-react-appというreact環境が簡単に用意できるものをfacebookが用意しているので、それを使います。
ざっくり説明
npm install -g create-react-app
create-react-app react-calendar-sample
cd react-calendar-sample
npm start
http://localhost:3000/
へアクセス
詳しくはこちら!
https://reactjs.org/blog/2016/07/22/create-apps-with-no-configuration.html
Vue.js
vue-cliというコマンドラインツールがあるので、それで作ります。
ざっくり説明
npm install -g vue-cli
vue init webpack vue-calendar-sample
cd vue-calendar-sample
-
npm install
(時間かかる) 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は書きやすいですね。
とはいえReactもそれほど手順が面倒ではないので、大きな問題ではないと思います。
react css
とかで検索すると、CSS in JSの話が出てくるのですが、あれはどうにもしっくりきません...
後一応の注意点としては、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">< 前</button>
<button styleName="button" style={styles.buttonRight} onClick={this.handleChangeCalendarPage} data-page="1">次 ></button>
</div>
)
Vue.js
親→子
親から子供への値の渡し方はReactとほぼ一緒です。
コンポーネントを定義時に属性として値を渡してあげます。
子→親
this.$parent.hogehoge
で親コンポーネントの値にアクセスできます
$parentで親の値を見に行くのはアンチパターンのようでした。
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)">< 前</button>
<button class="button-right" v-on:click="handleCalendar('1', fullDate)">次 ></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">< 前</button>
<button styleName="button" style={styles.buttonRight} onClick={this.handleChangeCalendarPage} data-page="1">次 ></button>
Vue.js
メソッド内でdataの値を更新して、無限ループに陥る
そりゃそーだろっていう話ですが
カレンダーコンポーネント内でループさせるたびに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トレンド
青い線がVue.js、赤い線がReact
Vue.jsの方がトレンドとしてはキテます。
Twitterフォロワー数
vue.js: 約5万3千
https://twitter.com/vuejs
react: 約19万
https://twitter.com/reactjs
圧倒的React
Qiita投稿数
雰囲気そのうちVue.jsが抜きそうです。