
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)の方が独自の書き方となるので、初めてのケースですとやや学習コストが高いのかなと思いました。両方扱う場合にはしっかりと違いを把握しておきたいですね。
categories
- WordPress(61)
- JavaScript(49)
- Vue.js(48)
- CSS(46)
- jQuery(34)
- Nuxt.js(22)
- Sass(21)
- Gulp(17)
- webpack(16)
- PHP(14)
- HTML(14)
- GSAP(11)
- React(10)
- Vite(9)
- Pug(9)
- Laravel(8)
- Cordova(8)
- Linux(7)
- Next.js(6)
- Apache(5)
- Shopify(4)
- REST API(4)
- Node.js(4)
- Smarty(3)
- Git(3)
- JSON(3)
- その他プログラミング(3)
- Google Apps API(3)
- Bootstrap(3)
- 環境構築・システム(3)
- TypeScript(2)
- EJS(2)
- RMS(2)
- Docker(2)
- Instagram API(2)
- Excel VBA(2)
- SVG(1)
- Dart(1)
- ECカートASP(1)
- Markdown(1)
- AMP(1)
- Nginx(1)
- XML(1)
- shellscript(1)
- Ruby(1)
- MySQL(1)