こちらの記事は、『I created the exact same app in React and Vue. Here are the differences.』の和訳になります。
はじめに
私はVueを仕事で使ったことがあるので、Vueの動作について十分に理解しています。しかし、隣の芝生は青く見えるものです。私は隣の芝生 - Reactにも興味をもちました。
私はReactのドキュメントを読み、いくつかのチュートリアルビデオを見ました。それらはすべて素晴らしいものでしたが、私が本当に知りたかったことは、ReactとVueとの違いでした。といっても、仮想DOMやページレンダリングの違いを知りたいという意味ではありません。私には、時間をかけてコードを説明してくれる人が必要だったのです!私は時間をかけて、VueとReact(またはWeb開発全般)を知らない人でも両者の違いがよく分かるような記事を探しました。
残念ながら、これを取り上げたものは見つかりませんでした。そこで私は、似ているところと違うところを知るために自分で記事を作成する必要があることに気づき、全体のプロセスを文書化して残すことにしました。
私は、ユーザがリストに項目を追加・削除できる、普通のToDoアプリを作ってみることにしました。どちらのアプリケーションも、デフォルトのCLIを使用して構築しました(Reactの場合はcreate-react-app、Vueの場合はvue-cliを使いました)。ちなみにCLIは、Command Line Interfaceの略です。🤓
まずは、2つのアプリケーションの見た目を確認しましょう。
両方のアプリのCSSコードはまったく同じですが、配置場所に違いがあります。それを念頭に置いて次に両方のアプリケーションのファイル構造を見てみましょう。
ファイル構造がほとんど同じだということが分かるでしょう。唯一の違いは、Reactアプリには3つのCSSファイルがあるのに対し、Vueアプリには一つもないことです。create-react-appでは、React コンポーネントにスタイルを保持するための補助ファイルをもたせるのに対して、VueCLIは包括的なアプローチを採用しており、スタイルが実際のコンポーネントファイルの中で定義されているからです。
最終的には、どちらも同じことを実現します。ReactまたはVueでは、CSSの構造を変えることはできません。それはひとえに、個人的な好みにかかっています - CSSがどのように構成されるべきかについては、CSSコミュニティで多く議論されているのを耳にすることもあるでしょう。いまは、それぞれのCLIが展開する形に従いましょう。
先に進む前に、典型的なVueおよびReactコンポーネントがどのようなものか、ざっと見てみましょう。
さて、本題に入りましょう!
データの変更方法
まず最初に、「データを変更する」とはどういう意味でしょうか?少し技術的に聞こえませんか?それは基本的には、単に私たちが保存したデータを変更するという意味です。
ある人の名前をJohnからMarkに変更したい場合は、「データを変更する」 ことになります。ここにReactとVueの大きな違いがあります。
Vueは本質的にデータを自由に更新できるデータオブジェクトを作成しますが、Reactはステートオブジェクトを作成するため、更新するのに少し手間がかかります。
つまり、Reactは意味があって手間のかかる実装をしているわけです。もう少し、掘り下げてみましょう。最初に、VueのデータオブジェクトとReactのステートオブジェクトを比べます。
左側がVueデータオブジェクト、右側がReactステートオブジェクトです
ラベルが異なるだけで、同じデータを両方に渡しています。初期データをコンポーネントに渡す方法は非常によく似ています。しかし、前述のように、このデータを変更する方法は両方のフレームワークで異なります。
name: ‘Sunilというデータがあるとしましょう。
Vueではthis.nameで参照します。また、**this.name = 'John'**で更新することもできます。この操作で、私の名前がJohnに変わることになります。私自身、Johnと呼ばれることについて自分がどのように感じるか分かりませんが...まぁ、そういうこともあるでしょう。😅
Reactではthis.state.name を使用して、同じデータを参照します。ここでの主な違いは、 **this.state.name = 'John'**という単純な書き込みができないことです。Reactには、意図しない変更を防ぐための制限があるからです。Reactでは **this.setState({name: 'John'})**のように記述します。
Vueも同じことが実行されますが、Vueではデータが更新されるたびにデフォルトでVueが作成したsetStateを組み込むため、余分な書き込みが発生します。つまり、ReactはsetStateとそれに続く更新済みのデータを必要としますが、Vueはユーザがデータオブジェクトの値を更新すると仮定して動作します。なぜ、ReactはsetStateを要求するのでしょうか?Revanth Kumarに説明してもらいましょう。
状態が変わるたびに、Reactが特定のライフサイクルフック、たとえば、 componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate、render、componentDidUpdateなどを再実行したいからです。setState関数を呼び出したときに状態が変化したことが分かります。状態を直接変更した場合、Reactは、変更やライフサイクルフックが実行する内容などを追跡するために、さらに多くの作業を行う必要があります。Reactはそれを簡素化するために、setStateを使うのです。
さて、両方のToDoアプリに新しいアイテムを追加する方法を見ながら、本題に入りましょう。
新しいToDoアイテムの作成方法
Reactの場合
createNewToDoItem = () => {
this.setState( ({ list, todo }) => ({
list: [
...list,
{
todo
}
],
todo: ''
})
);
};
Reactではinputフィールドにvalueと呼ばれる属性があります。valueはVueが提供する双方向バインディングのように、いくつかの機能を使用して自動的に更新されます(双方向バインディングはVueの説明で出てきます)。この機能を利用するには、onChangeイベントのリスナーをinputフィールドに設定します。それでは、ソースコードを見ていきましょう。
<input type="text"
value={this.state.todo}
onChange={this.handleInput}/>
handleInput関数は、入力フィールドの値が変わるたびに実行されます。inputフィールドに値を設定することで、ステートオブジェクトが持っているtodoを更新します。handleInput関数は次のように実装します。
handleInput = e => {
this.setState({
todo: e.target.value
});
};
ユーザが新しいアイテムを追加するためにページ上の+ボタンを押すたびに、 createNewToDoItem関数は、this.setStateに関数を渡して実行します。この関数には2つのパラメータがあります。1つ目はステートオブジェクトのリスト配列全体、2つ目は(handleInput関数によって更新される) todo です。その後、関数により新しいオブジェクトが返されます。新しいオブジェクトは前のリストをすべて含み、最後にtodoが追加されます。リストへの追加はスプレッド演算子(GoogleのES6 syntax を参照)を使用しています。
最後に todoに空白の文字列を設定すると、inputフィールドのvalueが自動的に更新されます。
Vueの場合
createNewToDoItem() {
this.list.push(
{
'todo': this.todo
}
);
this.todo = '';
}
Vueでは、 inputフィールドにv-modelと呼ばれるハンドルがあるので、双方向バインディングを実行することができます。それでは、見ていきましょう。
<input type = "text" v-model = "todo" />
V-Modelは、このフィールドの入力をtoDoItemというデータオブジェクトにあるキーに結びつけます。ページがロードされると、toDoItemは空の文字列に設定されます(例: todo:"")todo: 'add some text here' のようなデータがすでにそこにある場合、inputフィールドにはadd some text here が設定されます。inputフィールド内に入力したテキストはtodoの値にバインドされます。これが双方向バインディングの効果です。(inputフィールドはデータオブジェクトを更新できますし、データオブジェクトはinputフィールドを更新できます)。
createNewToDoItem()の実装を見ると、 todoをリストの配列にpushし、todoを空の文字列で更新していることが分かります。
リストからの削除方法
Reactの場合
deleteItem = indexToDelete => {
this.setState(({ list }) => ({
list: list.filter((toDo, index) => index !== indexToDelete)
}));
};
deleteItem関数はToDo.jsの中にあるので、最初にdeleteItem関数を**<ToDoItem />のprops**として渡すことで、 ToDoItem.jsの中で簡単に参照することができました。
<ToDoItem deleteItem = {this.deleteItem.bind(this、key)} />
この操作によって、まず関数が渡されて子要素に対してアクセスができるようになります。keyを渡してthisにバインドしていることが分かるでしょう。クリック時にkeyを使って、ToDoItemが削除しようとするものを区別できるようにしています。ToDoItemコンポーネントの内部では次のコードを実装します。
<div className =” ToDoItem-Delete” onClick = {this.props.deleteItem}> - </div>
this.props.deleteItemを参照することで親コンポーネント内にある関数を使用することができます。
Vueの場合
onDeleteItem(todo){
this.list = this.list.filter(item => item !== todo);
}
Vueでは、次の3つのステップを実施する必要があります。
まず、関数を呼び出したい要素を作ります。
<div class=”ToDoItem-Delete” @click=”deleteItem(todo)”>-</div>
次に、子コンポーネント(この場合は、ToDoItem.vue)にemit関数を作ります。
deleteItem(todo) {
this.$emit('delete', todo)
}
最後に、 ToDo.vueの中にToDoItem.vueを追加すると、実際に関数を参照していることが分かります。
<ToDoItem v-for="todo in list"
:todo="todo"
@delete="onDeleteItem" // <-- this :)
:key="todo.id" />
これがカスタムイベントリスナーと呼ばれるものです。'delete'という文字列でemitがトリガーされる場合は常に起動され、onDeleteItemが呼び出されます。この関数はToDoItem.vueではなくToDo.vueの中にあります。この関数では、データオブジェクト内部のTODO配列をフィルタリングし、クリックされたアイテムを取り除きます。
なお、Vueでは次のように**@click listenerに$emit**の部分を記述するだけでも実現できるということを覚えておいてください。
<div class=”ToDoItem-Delete” @click=”$emit(‘delete’, todo)”>-</div>
この書き方だと、作業を3ステップから2ステップに減らすことができますが、どちらを選択するかは個人の好みです。
まとめると、Reactでは子コンポーネントはthis.propsを介して親の関数にアクセスするのが一般的です。Vueでは親コンポーネントが待ち受けるイベントを子要素がemitします。
イベントリスナーの渡し方
Reactの場合
クリックイベントなどの単純なイベントリスナーは簡単です。次の例では、新しいToDoアイテムを作成するボタンのクリックイベントを作成しています。
<button className=”ToDo-Add” onClick={this.createNewToDoItem}>+</div>.
これはとても簡単で、インラインのonClickをピュアなJavascriptで扱う方法に似ています。イベントリスナーを設定して入力キーを押したことを検出するには少し手間がかかります。次のように、inputタグでonKeyPressイベントを処理する必要があります。
<input type=”text” onKeyPress={this.handleKeyPress}/>.
この関数は、Enterキーが押されたことを認識すると、常にcreateNewToDoItem関数を呼び出します。
handleKeyPress = (e) => {
if (e.key === ‘Enter’) {
this.createNewToDoItem();
}
};
Vueの場合
Vueではとても簡単です。@から始まるエイリアスを使用して実行したいイベントリスナーを指定します。たとえば、クリックイベントリスナーを追加するには次のように記述します。
<button class=”ToDo-Add” @click=”createNewToDoItem()”>+</div>
注: @click、は実際には、v-on:clickを書き込むための省略形です。Vueイベントリスナーの素晴らしいところは、イベントリスナーが複数回トリガーされるのを防ぐ.onceのように、それらにつなげるものがたくさんあることです。キーストロークを処理するために特定のイベントリスナーを書き込むためには、ショートカットが多数あります。ReactではEnterキーが押されたときに新しいToDoアイテムを作成するためのイベントリスナーを作るのに手間がかかりましたが、Vueでは、簡単につくることができました。
<input type=”text” v-on:keyup.enter=”createNewToDoItem”/>
子コンポーネントにデータを渡す方法
Reactの場合
Reactでは、子コンポーネントが作成された時点で、以下のように子コンポーネントにpropsを渡します。
<ToDoItem key={key} item={todo} />
ToDoItemコンポーネントに2つのpropsが渡されたことが分かります。これ以降は、this.propsを介して子コンポーネントでそれらを参照できます。そのため、item.todo propsにアクセスするには、ただ、this.props.itemを呼び出せばよいのです 。
Vueの場合
Vueでは、子コンポーネントが作成された時点で、以下のように子コンポーネントにpropsを渡します。
<ToDoItem v-for="todo in list"
:todo="todo"
:key="todo.id"
@delete="onDeleteItem" />
完了したら、次のように子コンポーネントのprops 配列に渡します。**props:['todo']これらは子コンポーネントの中で名前で参照することが可能で、この場合は'todo '**です。
親コンポーネントにデータを戻す方法
Reactの場合
最初に子コンポーネントを呼び出す場所でprops として参照することで、子コンポーネントに関数を渡します。次に、this.props.whateverTheFunctionIsCalledを参照して、onClickなどの方法で子コンポーネントについて関数をさらに呼び出し、親コンポーネントにある関数が起動されます。このプロセス全体の例は、 リストからの削除方法のセクションにあります。
Vueの場合
子コンポーネントでは、親関数に値を返す関数を書くだけです。親コンポーネントで、その値がemitされたときに呼び出される関数を作成すると、関数呼び出しがトリガーされます。このプロセス全体の例は、リストからの削除方法のセクションにあります。
これでOKです!🎉
これまで、データの追加、削除、変更、propsの形式における親コンポーネントから子コンポーネントへのデータの受け渡し、およびイベントリスナーの形式での子コンポーネントから親コンポーネントへのデータ送信を見て来ました。
もちろん、他にもReactとVueの間には、小さな違いや癖が多数ありますが、この記事の内容が、両方のフレームワークがどのように機能するのかを理解するための基礎として、少しでも役立つことを願っています。
翻訳協力
Author: Sunil Sandhu(https://medium.com/@sunilsandhu)
Thank you for letting us share your knowledge!
記事選定: @takitakis
翻訳/技術監査: Momoko
Markdown化: @aoharu