ReactとVueってどう違う?全く同じアプリをReactとVueで作成してみて分かった相違点
Post on:2018年9月6日
日常的にVueを使用している開発者が、Reactはどうなのだろうと思い、ReactとVueで全く同じアプリを作成した時のそれぞれの工程を比較して分かった相違点を紹介します。
I created the exact same app in React and Vue. Here are the differences.
下記は各ポイントを意訳したものです。
※当ブログでの翻訳記事は、元サイト様にライセンスを得て翻訳しています。
- 隣の家の芝生は青く見える
- VueとReactで作成したアプリの見た目を比較
- VueとReactはデータをどのように変更させるか
- アイテムの新規作成
- アイテムの削除
- イベントリスナーを渡す
- 子コンポーネントにデータを渡す
- 親コンポーネントにデータを戻す
- 終わりに
隣の家の芝生は青く見える
私は現在の職場でVueを使用しており、Vueがどのように機能するかかなり理解していると思います。しかし、隣の家の芝生はどんなものなのか非常に興味があります、その芝生とはReactのことです。
Reactのドキュメントを読んで、チュートリアルのビデオをいくつか見てみました。それは参考になったのですが、私が本当に知りたかったのは、ReactとVueがどのように異なるのかということでした。異なるというのは、仮想DOMとかレンダリングなどのことではありません。コードレベルで具体的にどう違うのかが知りたいのです。
しかし、いくら探しても見つけることができませんでした。そこで私は、類似点と相違点を知るためには、自分自身で記事を書かなければならないことを認識しました。この記事がここに存在するように、私はプロセス全体をドキュメント化しました。
VueとReact
ドキュメントを書くにあたり、TODOリストのアプリを作成しました。機能は、アイテムを加えたり、削除できるだけの簡単なアプリです。どちらもデフォルトのCLI(Reactではcreate-react-app、Vueではvue-cli)を使用しました。
※CLIとは、Command Line Interface(コマンド・ライン・インターフェース)の略。
VueとReactで作成したアプリの見た目を比較
左: Vueで作成したアプリ、右: Reactで作成したアプリ
CSSのコードは両方とも全く同じですが、置き場所が異なります。そのことを念頭において、アプリのファイル構造を見てみましょう。
左: Vueのファイル構造、右: Reactのファイル構造
VueとReactで、ファイル構造はほとんど同じです。唯一違うのは、Reactには3つのCSSファイルがあることです。VueにはCSSファイルが一つもありません。その理由は、create-react-appではReactコンポーネントがスタイルを保持するためのファイルを持ち、Vue CLIでは実際のコンポーネントファイル内でスタイルが宣言されている包括的なアプローチを採用しているからです。
2つは共に同じことを達成しており、ReactとVueであなたのCSSを違った方法で構築することはできません。これは完全に個人の好みでしょう、CSSをどのように構造化するべきかは、開発者コミュニティで活発に議論されています。現時点では、両方のCLIに配置された構造に従います。
先に進む前に、VueとReactの典型的なコンポーネントを見ておいてください。
Vueのコンポーネント
Reactのコンポーネント
VueとReactはデータをどのように変更させるか
まずは「データの変更」とは、何を意味するでしょうか? 変更とは、保存したデータを変えることです。例えば、名前の値をJohnからMarkに変えたいのであれば、「データを変更させる」ことになります。このやり方が、ReactとVueでは大きく異なります。
Vueでは基本的にdataオブジェクトを作成しますが、データを自由に更新できます。Reactではstateオブジェクトを作成し、それを更新するには少し作業が必要となります。Reactのこの少しの作業には、正当な理由があります。まずは、VueのdataオブジェクトとReactのstateオブジェクトを見てみましょう。
左: Vueのdataオブジェクト、右: Reactのstateオブジェクト
ラベルは異なっていますが、同じデータを渡しています。つまり、最初のデータをコンポーネントに渡すことは非常によく似ているということです。しかし、前述したように、このデータをどのように変えるかは、両方のフレームワークによって異なります。
例えば、名前が「Sunil」というデータ要素があるとします。
Vueでは、「this.name」で参照できます。「this.name = 'John'」と呼び出すことで、更新することができます。これで名前は「John」に変更されました。
Reactでは、「this.state.name」で同じデータを参照します。ここでの主な違いは、Reactには制限があるため、単に「this.state.name = 'John'」と書くことができないということです。Reactでは「this.setState({name: 'John'})」と書きます。
これはVueで達成したことと同じですが、Vueはデータが更新されるたびにsetStateの自身のバージョンを組み合わせているため、追加の書き込みがあります。つまり、ReactではsetStateとその内部の更新データを必要としますが、Vueではdataオブジェクト内の値を更新すると仮定して実行します。なぜReactではVueと同じにしないのでしょうか? そしてなぜsetStateが必要なのでしょうか? この説明はRevanth Kumarに委ねます。
それは、Reactがstateが変わるたびに特定のフックを再実行しようとしているためです。例えば、componentWillReceiveProps, shouldComponentUpdate, componentWillUpdate, render, componentDidUpdate, whenever state changes など。setState関数を呼び出すと、stateが変更されたことが分かるでしょう。もし、stateを直接的に変更させた場合、Reactは変更を追跡し、フックを実行するなど、もっと多くの作業を行う必要があります。それを簡単にするために、ReactはsetStateを使用します。
一方はシンプルじゃないね
データの変更について、VueとReactの相違を理解できたので、アプリ作成に戻ります。
アイテムの新規作成
Reactの場合
1 2 3 4 5 6 7 8 9 10 11 12 |
createNewToDoItem = () => { this.setState( ({ list, todo }) => ({ list: [ ...list, { todo } ], todo: '' }) ); }; |
Reactの場合では、inputフィールドにvalueという属性があり、このvalueは双方向バインディング(説明は後述)を作成するために結合された2つの関数を使用して自動的に更新されます。onChangeイベントリスナをinputフィールドに加えることによって、双方向バインディングを作成します。どのようになるか、inputフィールドのコードを見てましょう。
1 2 3 |
<input type="text" value={this.state.todo} onChange={this.handleInput}/> |
handleInput関数は、inputフィールドのvalueが変更されるたびに実行されます。これは、inputフィールドに何かが入力されると、stateオブジェクト内にあるtodoを更新します。関数は、下記のようになります。
1 2 3 4 5 |
handleInput = e => { this.setState({ todo: e.target.value }); }; |
ユーザーが+ボタンを押して新しいアイテムを追加すると、createNewToDoItem関数がthis.setStateを実行し、関数に渡します。この関数は2つのパラメータを受け取り、1つ目はstateオブジェクトのリスト配列全体、2つ目はtodo(handleInput関数で更新)です。この関数は新しいオブジェクトを返し、新しいオブジェクトは前のリスト全体を含み、最後にtodoを追加します。リスト全体は、スプレッド演算子を使用して追加されます。
最終的には、todoを空の文字列に設定して、inputフィールド内のvalueを自動的に更新します。
Vueの場合
1 2 3 4 5 6 7 8 |
createNewToDoItem() { this.list.push( { 'todo': this.todo } ); this.todo = ''; } |
Vueの場合では、inputフィールドにv-modelと呼ばれるハンドルを使用します。このハンドルにより、双方向バインディングを行うことができます。inputフィールドのコードを見てましょう。
1 |
<input type="text" v-model="todo"/> |
v-modelは、このフィールドに入力された値をtoDoItemというdataオブジェクトのキーに結びつけられます。ページが読み込まれると、toDoItemには「todo: ''」のような空の文字列が設定され、「todo: 'add some text here'」のようなデータが既にある場合は、inputフィールドにテキストを追加して読み込みます。空の文字列に戻ると、inputフィールド内に入力したテキストはtodoの値にバインドされます。これは実質的に、双方向バインディングと同じです(inputフィールドはdataオブジェクトを更新でき、dataオブジェクトはinputフィールドを更新できます)。
createNewToDoItem()コードブロックでは、todoの内容はlistの配列にプッシュされ、todoを空の文字列に更新することが分かります。
アイテムの削除
Reactの場合
1 2 3 4 5 |
deleteItem = indexToDelete => { this.setState(({ list }) => ({ list: list.filter((toDo, index) => index !== indexToDelete) })); }; |
deleteItem関数はToDo.jsの中にありますが、ToDoItem.js内の参照を最初に作成することができます。deleteItem関数を<ToDoItem/>のpropとして渡すには次のように書きます。
1 |
<ToDoItem deleteItem={this.deleteItem.bind(this, key)}/> |
このコードではまず、子にアクセスするために関数を渡します。ここではkeyのパラメータを渡すだけでなく、thisもバインドしていることが分かります。keyとして、どのToDoItemがクリックされたときに削除しようとしているかを区別するために関数が使用するものがあります。次に、ToDoItemコンポーネント内で、次の処理を行います。
1 |
<div className=”ToDoItem-Delete” onClick={this.props.deleteItem}>-</div> |
親コンポーネント内にある関数を参照するために必要なのは、this.props.deleteItemを参照するだけでした。
Vueの場合
1 2 3 |
onDeleteItem(todo){ this.list = this.list.filter(item => item !== todo); } |
Vueの場合では、少し違ったアプローチが必要です。ポイントは、3つあります。
第1に、関数を呼び出す要素を用意します。
1 |
<div class=”ToDoItem-Delete” @click=”deleteItem(todo)”>-</div> |
次に、子コンポーネント(この場合はToDoItem.vue)内のメソッドとしてemit関数を作成する必要があります。これは次のようになります。
1 2 3 |
deleteItem(todo) { this.$emit('delete', todo) } |
これに加えて、ToDo.vue内にToDoItem.vueを追加すると、実際に関数を参照することに気づくでしょう
1 2 3 4 |
<ToDoItem v-for="todo in list" :todo="todo" @delete="onDeleteItem" // <-- this :) :key="todo.id" /> |
これは、カスタムイベントリスナーと呼ばれるものです「delete」の文字列でemitがトリガされるあらゆる状況で実行されます。イベントが起きると、onDeleteItemという関数が呼び出されます。この関数は、ToDoItem.vueではなくToDo.vue内にあります。この関数は前述のように、dataオブジェクト内のtodo配列をフィルタリングして、クリックされたアイテムを削除します。
またVueの場合では、@clickに$emit()を書くだけでも大丈夫です。
1 |
<div class=”ToDoItem-Delete” @click=”$emit(‘delete’, todo)”>-</div> |
この記述で工程を減らすことができますが、どちらを採用するかは個人の好みです。
まとめると、Reactでは子コンポーネントはthis.propsを介して親の関数にアクセスします(かなり一般的な方法)。Vueでは親コンポーネント内で通常収集されるイベントを子から送出する必要があります。
イベントリスナーを渡す
Reactの場合
クリックイベントなどのシンプルなイベントリスナーは簡単です。ここでは、TODOリストに新しいアイテムを作成するボタンのクリックイベントを例に取ります。
1 |
<div className=”ToDo-Add” onClick={this.createNewToDoItem}>+</div>. |
コードは非常に簡単で、onClickにインラインのJavaScriptで処理しているかのように見えます。Vueで説明したように、enterキーが押されるたびにイベントリスナーを設定するのには少し時間がかかりました。下記のように、input要素に記述されたonKeyPressイベントが処理されることを必要としていました。
1 |
<input type=”text” onKeyPress={this.handleKeyPress}/>. |
この関数はenterキーが押されたことを認識するたびに、createNewToDoItem関数を呼び出します。
1 2 3 4 5 |
handleKeyPress = (e) => { if (e.key === ‘Enter’) { this.createNewToDoItem(); } }; |
Vueの場合
Vueでは、非常に簡単です。@を使用して、次に実行するイベントリスナーのタイプを記述するだけです。例えば、クリックイベントを加えるには、下記のように書きます。
1 |
<div class=”ToDo-Add” @click=”createNewToDoItem()”>+</div> |
注: @clickは、「v-on:click」の省略形です。
Vueのイベントリスナーの良い点は、イベントリスナーが複数回トリガーされないようにする.onceなど、連鎖できることです。また、キーストロークを処理するために特定のイベントリスナーを作成する場合には、いくつかのショートカットがあります。
enterキーが押されたときに、Reactでイベントリスナーを作成して新しいアイテムを作成するのにはかなり時間がかかりました。Vueでは、非常に簡単に書くことができます。
1 |
<input type=”text” v-on:keyup.enter=”createNewToDoItem”/> |
子コンポーネントにデータを渡す
Reactの場合
Reactの場合では、作成されたポイントで子コンポーネントにpropsを渡します。
1 |
<ToDoItem key={key} item={todo} /> |
このコードでは、ToDoItemコンポーネントに2つのpropsが渡されているのを確認できます。これにより、this.propsを介して子コンポーネントでそれらを参照することができます。item.todo propにアクセスするには、単にthis.props.itemを呼び出すだけです。
Vueの場合
Vueの場合では、子コンポーネントが作成された時点でpropsを渡します。
1 2 3 4 |
<ToDoItem v-for="todo in list" :todo="todo" :key="todo.id" @delete="onDeleteItem" /> |
このコードでは、子コンポーネントにprops: ['todo']のように配列でpropsを渡しています。これらの名前は子で参照することができ、この場合は「'todo'」です。
親コンポーネントにデータを戻す
Reactの場合
Reactでは最初に、子コンポーネントと呼ばれる場所のpropとして参照することによって、子コンポーネントに関数を渡します。次に、this.props.whateverTheFunctionIsCalledを参照して、onClickなどを使用して、関数の呼び出しを子関数に加えます。これにより、親コンポーネントにある関数がトリガーされます。
このプロセスの例は、「アイテムの削除」で見ることができます。
Vueの場合
Vueでは子コンポーネントで、親の関数に値を返す関数を書くだけです。親コンポーネントでは、その値がいつ呼び出されるかを監視する関数を作成します。この関数は、関数の呼び出しをトリガーにできます。
このプロセスの例は、「アイテムの削除」で見ることができます。
終わりに
どのようにアイテムを加えて、削除して、変更して、親から子へデータを渡し、子から親にデータを渡す方法を見てきました。ReactとVueでたくさんの相違点がありますが、2つのフレームワークがどのように扱うのかを理解するための助けになると思います。
最後に、ReactとVueで作成したアプリのコードを紹介します。
sponsors