You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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>

关键修改说明

  1. 恢复FormField Mixin:重新引入Nova的FormField mixin,保留字段核心计算属性(如hasError、firstError),避免模板逻辑报错。
  2. 适配子组件获取逻辑:通过instance.parent.ctx获取父组件上下文,沿用原Vue2的层级遍历逻辑,替代不可靠的VNode结构访问。
  3. 精准监听响应式属性:根据子组件的API风格,分别从setupState(Composition API)或ctx(Options API)中获取目标属性,完全匹配Vue2中$watch的深度监听、立即执行特性。
  4. 兼容customProperty配置:保留原逻辑中对customProperty的支持,可灵活指定监听的子组件属性。

内容的提问来源于stack exchange,提问作者Mak_091

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 03:17:03