Vue3 中子组件早于父组件触发的问题及解决方案

在使用 Vue3 开发过程中,我们经常会遇到子组件的 watch 早于父组件的逻辑触发的问题。这会导致一些意外的行为,影响用户体验。本文将通过具体的代码示例和分析,帮助大家理解这个问题的根源,并提供解决方案。

1. 问题描述

在 Vue3 中,子组件的 watch 事件可能会早于父组件的逻辑触发。这会导致子组件先执行某些操作,而父组件的逻辑尚未完成,从而引发一些问题。

1.1 示例代码

父组件代码
<template>
  <div>
    <ChildComponent :jdbcdata="subDetail" />
  </div>
</template>

<script setup>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';

const subDetail = ref({
  type: null,
  url: null,
  username: null,
  password: null,
  driver: null,
  edit_password: false,
});

const getDetail = async (id) => {
  try {
    const response = await instance.post('/datasource/detail/${id}');
    console.log(111); // 父组件逻辑
    subDetail.value = {
      type: response.type.toLowerCase(),
      url: response.url,
      username: response.username,
      password: response.password,
      driver: response.driver,
      edit_password: false,
    };
  } catch (error) {
    console.error(error);
  }
};
</script>
子组件代码
<template>
  <div>
    <el-form :model="detail">
      <el-form-item :label="$t('m.note')">
        <el-input v-model="detail.note"></el-input>
      </el-form-item>
      <el-form-item :label="$t('m.type')">
        <el-radio-group v-model="detail.dbApiType">
          <el-radio :label="1">JDBC</el-radio>
        </el-radio-group>
      </el-form-item>
    </el-form>
  </div>
</template>

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

const props = defineProps({
  jdbcdata: {
    type: Object,
    default: () => ({
      type: null,
      url: null,
      username: null,
      password: null,
      driver: null,
      edit_password: false,
    }),
  },
});

const detail = reactive({
  id: null,
  name: null,
  note: null,
  dbApiType: 'jdbc',
  url: '',
  username: '',
  password: '',
  type: '',
  edit_password: false,
  driver: null,
});

watch(() => props.jdbcdata, (val) => {
  console.log(222); // 子组件逻辑
  detail.type = val.type;
  detail.url = val.url;
  detail.username = val.username;
  detail.password = val.password;
  detail.driver = val.driver;
  detail.edit_password = false;
});
</script>

父组件中是

    <ChildComponent :jdbcdata="subDetail" />

子组件中是

const props = defineProps({
  jdbcdata: {
    type: Object,
    default: () => ({
      name: null,
      note: null,
      url: null,
      type: null,
      username: null,
      password: null,
      edit_password: false,
      driver: null,
      tableSql: null,
    }),
  },
});

1.2 问题现象

在上述代码中,子组件的 watch 事件会先于父组件的逻辑触发。具体表现为:

  1. 父组件调用 getDetail 方法获取数据。

  2. 子组件的 watch 事件会先触发,打印 222

  3. 父组件的 会后执行代码,会后打印111 。

这会导致子组件先执行 watch 逻辑,而父组件的修改子组件传值的逻辑尚未完成,从而引发一些意外的行为。

2. 问题分析

2.1 子组件早于父组件触发的原因

父组件传递给子组件的对象不是ref创建的,而是reative创建的,这会导致子组件监听该对象时,子组件的 watch 事件会监听 props.jdbcdata 的变化。由于 Vue3 的响应式机制,当父组件更新 subDetail 的值时,子组件会立即检测到 props.jdbcdata 的变化,从而触发 watch 事件。

2.2 具体原因

  • 子组件的 watch 事件监听的是 props.jdbcdata:当父组件更新 subDetail 的值时,子组件会立即检测到 props.jdbcdata 的变化。

  • Vue3 的响应式机制:Vue3 的响应式机制会立即通知所有依赖 props.jdbcdata 的组件,包括子组件的 watch 事件。

3. 解决方案

3.1 使用 ref 代替 reactive

在父组件中,使用 ref 代替 reactive 创建响应式对象。这样可以确保父组件的逻辑在子组件的 watch 事件触发之前完成。

修改后的父组件代码
<script setup>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';

const subDetail = ref({
  type: null,
  url: null,
  username: null,
  password: null,
  driver: null,
  edit_password: false,
});

const getDetail = async (id) => {
  try {
    const response = await instance.post('/datasource/detail/${id}');
    console.log(111); // 父组件逻辑
    subDetail.value = {
      type: response.type.toLowerCase(),
      url: response.url,
      username: response.username,
      password: response.password,
      driver: response.driver,
      edit_password: false,
    };
  } catch (error) {
    console.error(error);
  }
};
</script>

3.2 子组件代码保持不变

子组件代码保持不变,仍然使用 watch 监听 props.jdbcdata 的变化。

3.3 解决问题的关键

  • 使用 ref 创建响应式对象ref 的更新是通过替换 .value 实现的,而不是逐个属性修改。Vue 会在整个对象替换完成后才触发响应式更新。

  • 确保父组件逻辑先执行:通过使用 ref,父组件的逻辑会在子组件的 watch 事件触发之前完成。

4. 代码改进建议

4.1 使用 ref 创建响应式对象

在父组件中,使用 ref 创建响应式对象,而不是 reactive。这样可以确保父组件的逻辑在子组件的 watch 事件触发之前完成。

const subDetail = ref({
  type: null,
  url: null,
  username: null,
  password: null,
  driver: null,
  edit_password: false,
});

4.2 更新 subDetail 的值

在父组件中,通过替换 subDetail.value 来更新响应式对象的值。

subDetail.value = {
  type: response.type.toLowerCase(),
  url: response.url,
  username: response.username,
  password: response.password,
  driver: response.driver,
  edit_password: false,
};

4.3 子组件保持不变

子组件的代码保持不变,仍然使用 watch 监听 props.jdbcdata 的变化。

watch(() => props.jdbcdata, (val) => {
  console.log(222); // 子组件逻辑
  detail.type = val.type;
  detail.url = val.url;
  detail.username = val.username;
  detail.password = val.password;
  detail.driver = val.driver;
  detail.edit_password = false;
});

5. 总结

通过使用 ref 创建响应式对象,可以确保父组件的逻辑在子组件的 watch 事件触发之前完成。而使用reactive会造成属性的立即响应,会导致子组件早于父组件触发。通过使用 ref 创建响应式对象,可以避免子组件早于父组件触发的问题,提高代码的稳定性和用户体验。

希望本文能够帮助大家理解 Vue3 中子组件早于父组件触发的问题,并提供有效的解决方案。如果有任何问题,欢迎在评论区留言讨论。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值