Laravel Nova从Vue2迁移到Vue3:自定义组件$watch替换问题
Vue3(Laravel Nova4)中实现监听子组件属性变化替代Vue2 $watch
背景
正在将Laravel Nova3(基于Vue2)项目升级至Nova4(基于Vue3),已替换Vue3废弃的$parent/$children获取子组件,但Vue2中监听子组件属性的$watch功能无法直接复用。实际场景为自定义Role组件依赖Company字段:Company为空时Role隐藏,选中特定值时显示,需在Vue3中实现同等监听逻辑。
限制条件
- 无法通过
ref直接访问父/子组件(Laravel Nova限制) - 不采用Nova4的
dependOn方法,因组件需适配复杂资源场景
Vue2(Nova3)实现代码
<template> <div class="-mx-8 px-8"> <div v-for="condition in field.conditions" :key="condition.value"> <div v-if="condition.value == value"> <component v-bind="$props" :key="subfield" :field="subfield" :is="'form-' + subfield.component" v-for="subfield in condition.fields" /> <div class="help-text error-text mt-2 text-danger" v-if="hasError"> {{ firstError }} </div> </div> </div> </div> </template> <script> import { FormField } from 'laravel-nova' import { Errors } from 'form-backend-validation' export default { mixins: [FormField], props: { errors: { default: () => new Errors(), }, resource: { required: true }, resourceId: { required: true }, 'base-classes': { type: Object } }, data() { return { value: null, errorClass: 'border-danger', } }, mounted() { this.children.forEach(component => { if(component.field !== undefined && component.field.attribute === this.field.observed_field_name) { component.$watch(this.field.customProperty || 'value', (value) => { this.value = value }, {deep: true, immediate: true}); } }); }, computed: { parent() { let parent = this.$parent for (let i = 1; i < (this.field.depth || 1); i++) { parent = parent.$parent } return parent }, children() { let children = this.parent.$children for (let i = 1; i < (this.field.depth || 1); i++) { children = children.filter(c => c.$children.length).map(c => c.$children[0]) } return children }, }, } </script>
Vue3(Nova4)修改后实现代码
<template> <div class="-mx-8 px-8"> <div v-for="condition in field.conditions" :key="condition.value"> <div v-if="condition.value == value"> <component v-bind="$props" :key="subfield" :field="subfield" :is="'form-' + subfield.component" v-for="subfield in condition.fields" /> <div class="help-text error-text mt-2 text-danger" v-if="hasError"> {{ firstError }} </div> </div> </div> </div> </template> <script> import { ref, watch, onMounted, getCurrentInstance } from 'vue'; import { Errors } from 'form-backend-validation'; import { FormField } from 'laravel-nova'; export default { mixins: [FormField], props: { errors: { default: () => new Errors(), }, resource: { required: true }, resourceId: { required: true }, 'base-classes': { type: Object }, field: { type: Object, required: true } }, setup(props) { const value = ref(null); const errorClass = 'border-danger'; const instance = getCurrentInstance(); // 适配Vue3的父/子组件层级遍历逻辑 const getTargetChildren = () => { let parent = instance.parent.ctx; for (let i = 1; i < (props.field.depth || 1); i++) { parent = parent.$parent; } let targetChildren = parent.$children; for (let i = 1; i < (props.field.depth || 1); i++) { targetChildren = targetChildren.filter(c => c.$children.length).map(c => c.$children[0]); } return targetChildren; }; onMounted(() => { const targetChildren = getTargetChildren(); const watchKey = props.field.customProperty || 'value'; targetChildren.forEach(childInstance => { if (childInstance.props?.field?.attribute === props.field.observed_field_name) { // 兼容Composition/Options API风格的子组件属性获取 const targetValue = childInstance.setupState[watchKey] ?? childInstance.ctx[watchKey]; if (targetValue) { watch(() => targetValue, (newValue) => { value.value = newValue; }, { deep: true, immediate: true }); } } }); }); return { value, errorClass }; } } </script>
关键修改说明
- 恢复FormField Mixin:重新引入Nova的
FormFieldmixin,保留字段核心计算属性(如hasError、firstError),避免模板逻辑报错。 - 适配子组件获取逻辑:通过
instance.parent.ctx获取父组件上下文,沿用原Vue2的层级遍历逻辑,替代不可靠的VNode结构访问。 - 精准监听响应式属性:根据子组件的API风格,分别从
setupState(Composition API)或ctx(Options API)中获取目标属性,完全匹配Vue2中$watch的深度监听、立即执行特性。 - 兼容customProperty配置:保留原逻辑中对
customProperty的支持,可灵活指定监听的子组件属性。
内容的提问来源于stack exchange,提问作者Mak_091
相关产品推荐
相关产品推荐

