Vue.js(Options API)からReactへの移行でみる違いと比較 #5:コンポーネント間で値の受け渡し(props)
引き続き今回もReactとVue.jsでコードの比較をしていきたいと思います。第5回目になります本記事では、コンポーネント間で値の受け渡し方について注目してみます。いわゆるPorpsと呼ばれるもので、コンポーネントを扱う際には必ずと言って良いほど登場します。値を受け渡すことで、コンポーネント間での動的な処理ができるようになりますが、ReactとVue.jsでは少しコードの書き方も異なります。
(過去記事)
Vue.js(Options API)からReactへの移行でみる違いと比較 #1:コンポーネント
Vue.js(Options API)からReactへの移行でみる違いと比較 #2:メソッド・ステート
Vue.js(Options API)からReactへの移行でみる違いと比較 #3:テンプレート処理
Vue.js(Options API)からReactへの移行でみる違いと比較 #4:イベントハンドリング・バインディング
前回同様、これまでに引き続きサンプルとしてあげているアプリケーションのコードになります。ここではスタイルに関する記述は割愛しています。テンプレートと処理の部分だけを抜粋しています。
【App.vue(Vue.js)】※一部抜粋
コンポーネント間の値の受け渡しについては、このようにVue.jsとReactで少し違いが見られるようですね。どちらも慣れるとそうでもないのですが、Vue.js(Options API)の方が独自の書き方となるので、初めてのケースですとやや学習コストが高いのかなと思いました。両方扱う場合にはしっかりと違いを把握しておきたいですね。
<template> <h1>TODO List(Vue)</h1> <ul class="list"> <task-item v-for="(task, index) in tasks" :key="`${task}-${index}`" :task-data="{ index, task }" @delete-task="deleteTask" /> </ul> <input v-model="newTask" class="task-input" type="text" placeholder="タスクを入力" /> <button class="button-add" @click="addTask">追加</button> <div v-if="isError" class="error-message">タスクが空欄です</div> </template> <script> import TaskItem from './components/TaskItem.vue'; export default { components: { TaskItem }, data() { return { tasks: [], newTask: null, isError: false } }, created() { this.fetchData(); }, methods: { fetchData() { this.tasks = [ 'タスク1', 'タスク2', 'タスク3' ]; }, addTask() { if(this.newTask !== null) { this.tasks.push(this.newTask); this.newTask = null; this.isError = false; } else { this.isError = true; } }, deleteTask(payload) { this.tasks.splice(payload, 1); } } } </script>【TaskItem.vue(Vue.js)】※一部抜粋
<template> <li class="list-item"> <button class="button-delete" @click="emitDeleteTask(taskData.index)">削除</button> <span>{{ taskData.task }}</span> </li> </template> <script> export default { name: 'TaskItem', props: { taskData: { type: () => {} } }, methods: { emitDeleteTask(index) { this.$emit('delete-task', index) } } } </script>Reactも同じくサンプルのアプリケーションのソースコードを確認しておきます。テンプレートや処理の書き方などの差異は過去記事でもまとめています。 【App.jsx(React)】
import React, { useState, useEffect, useRef } from 'react'; import { TaskItem } from './components/TaskItem.jsx'; import AppCSS from './App.module.css'; export const App = () => { const [tasks, setTasks] = useState([]); const [newTask, setNewTask] = useState(null); const [isError, setError] = useState(false); const fetchData = () => { setTasks([ 'タスク1', 'タスク2', 'タスク3' ]); }; const dataBinding = (event) => { setNewTask(event.target.value) }; const refs = useRef(null); const formReset = () => { refs.current.value = null }; const addTask = () => { if(newTask !== null) { setTasks([...tasks, newTask]); setNewTask(null); setError(false); } else { setError(true); } }; const deleteTask = (payload) => { setTasks( tasks.filter((task, index) => index !== payload) ) }; useEffect(() => { fetchData(); }, []) return ( <> <h1>TODO List(React)</h1> <ul className={AppCSS['list']}> { tasks.map((task, index) => { return <TaskItem taskData={{ index, task }} key={`${task}-${index.toString()}`} methods={ { deleteTask } } /> }) } </ul> <input ref={refs} onChange={dataBinding} className={AppCSS['task-input']} type="text" placeholder="タスクを入力" /> <button onClick={() => { addTask(); formReset(); }} className={AppCSS['button-add']} >追加</button> { isError && <div className={AppCSS['error-message']}>タスクが空欄です</div> } </> ) };【TaskItem.jsx(React)】
import React from 'react'; import TaskItemCSS from './TaskItem.module.css'; export const TaskItem = (props) => { return ( <> <li className={TaskItemCSS['list-item']}> <button onClick={() => props.methods.deleteTask(props.taskData.index)} className={TaskItemCSS['button-delete']} >削除</button> <span>{props.taskData.task}</span> </li> </> ) };今回はコンポーネント間の値の受け渡し部分を見ていきますので、親子間のコンポーネントでどのような処理が行われているかに注目ですね。
1. props(親コンポーネントから子コンポーネントへの値受け渡し)
Vue.jsでは、親コンポーネントから子コンポーネントに値を受け渡す場合、「props」というオプションを使用します。親コンポーネント内のテンプレート上にある子コンポーネントへ、props名の属性を指定し、その値に受け渡すデータを指定します。この時、属性であるprops名はハイフン区切りのケバブケースで指定する必要があります。(参考記事:命名規則「キャメルケース」「スネークケース」「ケバブケース」についてまとめてみました)そして、子コンポーネント側ではpropsとして受け取ります。この時に受け渡された値の型や初期値、必須かどうかなどをオプションで指定することができます。子コンポーネント側ではprops名はキャメルケースで扱う点に注意します。受け渡された値はテンプレート上で展開したり、引数の値などに使えます。(参考記事:Vue.jsで親から孫まで多重階層になっているコンポーネント間で値を受け渡す)// Vue.js(Options API) // 親コンポーネント <template> <child-component :props-data="value" /> </template> <script> import ChildComponent from './ChildComponent.vue'; export default { components: { ChildComponent }, data() { return { value: 'hoge' } } } </script> // 子コンポーネント <template> <div>{{ propsData }}</div> </template> <script> export default { name: 'ChildComponent', props: { propsData: { type: String, default: '' } } } </script>Reactでは、関数コンポーネントの場合に親要素では同じように子コンポーネントにpropsとして値を受け渡します。Vue.jsとは異なりキャメルケースでの指定になります。そして子コンポーネント側では、コンポーネントの関数の引数でpropsを受け取ることができます。その引数内で、親コンポーネントで設定したprops名をキーにすることで受け渡された値を取得できます。Vue.jsと比べるとこちらの方がシンプルですね。
// React // 親コンポーネント import React, { useState } from 'react'; import { ChildComponent } from './ChildComponent.jsx'; export const ParentComponent = () => { const [value, setValue] = useState('hoge'); return ( <> <ChildComponent propsData={ value } /> </> ) }; // 子コンポーネント import React from 'react'; export const ChildComponent = (props) => { return ( <> <div>{props.propsData}</div> </> ) };
2. $emit(子コンポーネントから親コンポーネントへの値受け渡し)
続いて、先ほどのpropsとは逆に子コンポーネントから親コンポーネントへ値を受け渡す場合にはpropsではなく、「$emit」によって子コンポーネント側でカスタムイベントを作成し、そのイベントで親コンポーネント側のメソッドを発火させるようにします。その際に、引数で値を受け取ることができるので、これを利用することで子コンポーネントから親コンポーネントへ値を受け渡すことができます。(参考記事:Vue.jsで親から孫まで多重階層になっているコンポーネント間で値を受け渡す)つまり、子コンポーネントでのカスタムイベントを使って値を受け取る形になります。子から親への値の受け渡しには、$emit用のメソッドが必要になるのでやや複雑な印象を受けます。// Vue.js(Options API) // 親コンポーネント <template> <child-component @child-event="fetchValue" /> <div>{{ value }}</div> </template> <script> import ChildComponent from './ChildComponent.vue'; export default { components: { ChildComponent }, data() { return { value: '' } }, methods: { fetchValue(payload) { this.value = payload; }, } } </script> // 子コンポーネント <template> <button @click="emitFetchValue(value)">Emit!</button> </template> <script> export default { name: 'ChildComponent', data() { return { value: 'hoge' } }, methods: { emitFetchValue(emitted) { this.$emit('child-event', emitted) } } } </script>Reactの場合には、Vue.jsのような$emitというのは存在せず、propsで値と同じように、親コンポーネントで定義したメソッドも子コンポーネントへ受け渡すことができます。そして子コンポーネント側で受け取ったメソッドを実行することで、親コンポーネント側の値を更新することができます。こちらの方がスマートでわかりやすい印象がありますね。
// React // 親コンポーネント import React, { useState } from 'react'; import { ChildComponent } from './ChildComponent.jsx'; export const ParentComponent = () => { const [value, setValue] = useState(''); const fetchValue = (payload) => { setValue(payload); } return ( <> <ChildComponent propsFunction={fetchValue} /> <div>{ value }</div> </> ) }; // 子コンポーネント import React, { useState } from 'react'; export const ChildComponent = (props) => { const [value, setValue] = useState('hoge'); return ( <> <button onClick={() => { props.propsFunction(value) }} >Emit!</button> </> ) };
コンポーネント間の値の受け渡しについては、このようにVue.jsとReactで少し違いが見られるようですね。どちらも慣れるとそうでもないのですが、Vue.js(Options API)の方が独自の書き方となるので、初めてのケースですとやや学習コストが高いのかなと思いました。両方扱う場合にはしっかりと違いを把握しておきたいですね。
sponserd
keyword search
recent posts
- ViteでMarkuplintとPrettierを使える環境を構築する
ViteでMarkuplintとPrettierを使える環境を構築する
- ViteでStylelintとESlintを使える環境を構築する
ViteでStylelintとESlintを使える環境を構築する
- マウスオーバーしたセルを含む行列がハイライトするテーブルを作成する:has()擬似クラスの活用例
マウスオーバーしたセルを含む行列がハイライトするテーブルを作成する:has()擬似クラスの活用例
- ViteでVue.jsとVuex(Pinia)とVue Routerを使ってみる
ViteでVue.jsとVuex(Pinia)とVue Routerを使ってみる
- ViteでHandlebarsを使った複数ページの作成に使える外部JSONファイルのデータを読み込む
ViteでHandlebarsを使った複数ページの作成に使える外部JSONファイルのデータを読み込む
- ViteでTailwindCSSとテンプレートエンジンのHandlebarsを使ったページコーディング
ViteでTailwindCSSとテンプレートエンジンのHandlebarsを使ったページコーディング
- ViteでPostCSS周りの設定やSassを使う
ViteでPostCSS周りの設定やSassを使う
- フロントエンドの開発環境にVite + TypeScriptを導入する
フロントエンドの開発環境にVite + TypeScriptを導入する
categories