owned mediaウェブ制作に役立つコンテンツを発信中!

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)】※一部抜粋
<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)の方が独自の書き方となるので、初めてのケースですとやや学習コストが高いのかなと思いました。両方扱う場合にはしっかりと違いを把握しておきたいですね。
  • はてなブックマーク
  • Pocket
  • Linkedin
  • Feedly

この記事を書いた人

Twitter

sponserd

    keyword search

    recent posts

    • Twitter
    • Github
    contact usscroll to top
      • Facebook
      • Twitter
      • Github
      • Instagram