Vue.jsにおける親子コンポーネント間のデータ連携とUI操作

親子コンポーネントのデータ連携とイベント処理

Vue.jsアプリケーションでは、親コンポーネントがテーブルを表示し、子コンポーネントがモーダルダイアログとして動作する構成が一般的です。この設計では、コンポーネント間のデータ同期が重要になります。

親コンポーネントの実装例

<template>
  <div>
    <button @click="openDialog('create')">新規追加</button>
    <table>
      <tr v-for="item in items" :key="item.id">
        <td>{{ item.date }}</td>
        <td>{{ item.name }}</td>
        <td>
          <button @click="openDialog('update', item)">編集</button>
          <button @click="removeItem(item)">削除</button>
        </td>
      </tr>
    </table>
    <FormModal 
      :visible="modalConfig.visible"
      :title="modalConfig.title"
      :initialData="modalConfig.data"
      @submit="handleFormSubmit"
      @close="closeDialog"
    />
  </div>
</template>

<script>
import { reactive } from 'vue';
import FormModal from './FormModal.vue';

export default {
  components: { FormModal },
  setup() {
    const state = reactive({
      items: [
        { id: 1, date: '2023-01-01', name: 'サンプル1' },
        { id: 2, date: '2023-01-02', name: 'サンプル2' }
      ],
      modalConfig: {
        visible: false,
        title: '',
        data: {}
      }
    });

    function openDialog(mode, item = null) {
      state.modalConfig.visible = true;
      state.modalConfig.title = mode === 'create' ? '新規追加' : '編集';
      state.modalConfig.data = item ? { ...item } : {};
    }

    function closeDialog() {
      state.modalConfig.visible = false;
    }

    function handleFormSubmit(formData) {
      // フォームデータ処理ロジック
    }

    function removeItem(target) {
      // 削除確認ダイアログ表示
    }

    return {
      ...state,
      openDialog,
      closeDialog,
      handleFormSubmit,
      removeItem
    };
  }
};
</script>

子コンポーネントの実装例

<template>
  <dialog :open="isVisible">
    <h2>{{ title }}</h2>
    <form @submit.prevent="submitForm">
      <label>名称:
        <input v-model="formState.name" required>
      </label>
      <label>日付:
        <input v-model="formState.date" type="date" required>
      </label>
      <button type="submit">確定</button>
      <button type="button" @click="cancel">キャンセル</button>
    </form>
  </dialog>
</template>

<script>
import { reactive, watch } from 'vue';

export default {
  props: {
    visible: Boolean,
    title: String,
    initialData: Object
  },
  emits: ['submit', 'close', 'update:visible'],
  setup(props, { emit }) {
    const formState = reactive({
      name: '',
      date: ''
    });

    watch(() => props.visible, (isOpen) => {
      if (isOpen) resetForm();
    });

    watch(() => props.initialData, (newData) => {
      Object.assign(formState, newData || {});
    }, { immediate: true });

    function resetForm() {
      Object.keys(formState).forEach(key => {
        formState[key] = '';
      });
    }

    function submitForm() {
      emit('submit', { ...formState });
    }

    function cancel() {
      emit('update:visible', false);
      emit('close');
    }

    return {
      formState,
      submitForm,
      cancel
    };
  }
};
</script>

条件付きバリデーションの実装

追加操作時のみフォームバリデーションを適用する方法:

<script>
import { ref } from 'vue';

export default {
  setup() {
    const formRef = ref(null);
    const operationType = ref('');

    function openDialog(mode) {
      operationType.value = mode;
      if (formRef.value) {
        formRef.value.resetFields();
        formRef.value.clearValidate();
      }
    }

    async function submitForm() {
      if (operationType.value === 'create') {
        const isValid = await formRef.value.validate();
        if (!isValid) return;
      }
      // フォーム送信処理
    }

    return {
      formRef,
      openDialog,
      submitForm
    };
  }
};
</script>

編集中の行のハイライト表示

<template>
  <table>
    <tr 
      v-for="item in items" 
      :key="item.id" 
      :class="{ 'editing': item.editing }"
    >
      <!-- テーブル内容 -->
    </tr>
  </table>
</template>

<script>
export default {
  methods: {
    openDialog(mode, item) {
      if (mode === 'update') {
        this.items.forEach(i => i.editing = false);
        item.editing = true;
      }
    },
    closeDialog() {
      this.items.forEach(item => item.editing = false);
    }
  }
};
</script>

<style>
.editing {
  background-color: #f0f0f0;
}
</style>

リアクティブなデータ同期

親コンポーネントの状態変化に応じた子コンポーネントの更新:

<script>
export default {
  props: ['selectedId'],
  setup(props) {
    watch(() => props.selectedId, (newId) => {
      if (newId) fetchData(newId);
    });
    
    function fetchData(id) {
      // API呼び出し
    }
  }
};
</script>

タグ: vue.js コンポーネント設計 データバインディング UI操作 ElementPlus

5月29日 10:31 投稿