在使用 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
事件会先于父组件的逻辑触发。具体表现为:
-
父组件调用
getDetail
方法获取数据。 -
子组件的
watch
事件会先触发,打印222
。 -
父组件的 会后执行代码,会后打印
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 中子组件早于父组件触发的问题,并提供有效的解决方案。如果有任何问题,欢迎在评论区留言讨论。