父组件prop传向子组件的值,被子组件直接v-model绑定 功能不生效

发布于:2025-06-07 ⋅ 阅读:(21) ⋅ 点赞:(0)

隐式修改组件属性会导致功能异常

实际操作中发现,即便是父组件把简单数据通过prop传给了子组件,子组件再使用v-model绑定,也不行,响应式还是对异常

原vue2业务中存在组件定义某个类型为Object的属性,然后将该属性对象的属性值绑定给​​v-model​​的用法,在此种场景会导致隐式修改组件属性,vue3中是禁止的,此种方式目前不报错,但是响应式不正确生效,功能会不正常。请修改掉。

例子一:

针对常规将props属性赋值给组件内如 ​​input​​​的​​v-model​​​,因​​v-model​​会双向修改值,形成修改属性,此场景推荐用如下方式,两者均可:

方式1修改:

// vue2 ,vue2中会警告但是能运行正常,vue3中无法正常运行
<el-input v-model="searchValue"/>
props:{
  searchValue:{
    type:String,
    default:""
  }
}
// vue3可用如下替代方案,
// compoentA.vue
<el-input v-model="searchValueLocal"/>
props:{
  searchValue:{
    type:String,
    default:""
  }
},
computed:{
  searchValueLocal:{
		get(){
    	return this.searchValue;
    },
    set(val){
    	this.$emit("update:searchValue",val) // 更新searchValue值
    }
  }
}
// compoentB.vue使用 compoentA.vue
<compoent-b v-model:search-value="text"/>
data(){
  return{
  	text:''
  }
}

方式2修改
将​​v-model​​语法糖拆开

​​v-model​​语法糖本质是绑定​​modelValue​​,并绑定​​update:modelValue=val=>this.modelValue=val​​,导致形成属性修改,只要将语法糖拆开即可避免直接修改属性

// compoentA.vue
<el-input :model-value="modelValue" @update:model-value="handleSelectChange"/>
props:{
  modelValue:{
    type:String,
    default:""
  }
},
methods:{
	handleSelectChange(val){
    this.$emit('update:modelValue', val); // 值更新抛给父组件,由父组件来更新修改modelValue的值
  }
}

例子二:

如下代码:

1) 问题:定义了​​data​​​组件属性,但是将属性绑定给​​el-form​​​的​​model​​​属性,和各表单组件的​​v-model​​​;此时形成了隐式操作组件属性的问题,对于el-formmodel和各表单组件的​​v-model​​,无法形成响应式,会产生实际填写内容但是表单校验提示未输入内容,表单输入更新不及时的问题。

2) 解决:在组件内部直接修改属性的方式改掉,改成单向数据流或者在data中定义变量中转;同时注意,对于computed的计算属性(理论上是只读),也不能做值修改,会不触发响应式原理

<template>
  <div class="label-form">
    <el-form :model="data" label-position="top" :rules="rules" ref="labelForm" label-width="10rem" @submit.prevent>
      <el-form-item :label="$t('name')" prop="Name">
        <el-input @click.stop="focusTag" ref="tag" v-model="data.Name" v-input-permit.name :placeholder="$t('tag_name')"></el-input>
      </el-form-item>
      <el-form-item :label="$t('description')" prop="Describe">
        <el-input
          type="textarea"
          @click.stop="focusDesc"
          ref="desc"
          :count="128"
          :autosize="{ minRows: 2, maxRows: 4, maxWidth: 300 }"
          v-model="data.Describe"
          v-input-permit.name
          :placeholder="$t('description')"
        ></el-input>
      </el-form-item>
      <el-form-item class="form-buttons">
        <el-button type="primary" @click="onSubmit">{{ $t('btn_save') }}</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>

<script>
import rules from '@/Common/service/rule/validate-rules';
export default {
  name: 'LabelForm',
  props: {
    data: {
      type: Object,
      default() {
        return {
          Name: '',
          Describe: ''
        };
      }
    }
  },
  emits: ['onSubmit'],
  data() {
    return {
      rules: {
        Name: [rules.required, rules.name],
        Describe: [rules.textareaDescription]
      }
    };
  },
  methods: {
    onSubmit() {
      this.$refs['labelForm'].validate(async (valid) => {
        if (valid) {
          this.emit('onSubmit', this.data);
        } else {
          this.$refs['labelForm'].focusFirstField();
          return false;
        }
      });
    },
    reset() {
      this.$refs['labelForm'].resetFields();
    },
    focusTag() {
      const inputDom = this.$refs.tag.$el.querySelector('input');
      inputDom.focus();
    },
    focusDesc() {
      const inputDom = this.$refs.desc.$el.querySelector('textarea');
      inputDom.focus();
    }
  }
};
</script>

// ai帮改
<template>
  <div class="label-form">
    <el-form :model="formData" label-position="top" :rules="rules" ref="labelForm" label-width="10rem" @submit.prevent>
      <el-form-item :label="$t('name')" prop="Name">
        <el-input @click.stop="focusTag" ref="tagRef" v-model="formData.Name" v-input-permit.name :placeholder="$t('tag_name')"></el-input>
      </el-form-item>
      <el-form-item :label="$t('description')" prop="Describe">
        <el-input
          type="textarea"
          @click.stop="focusDesc"
          ref="descRef"
          :count="128"
          :autosize="{ minRows: 2, maxRows: 4 }"
          v-model="formData.Describe"
          v-input-permit.name
          :placeholder="$t('description')"
        ></el-input>
      </el-form-item>
      <el-form-item class="form-buttons">
        <el-button type="primary" @click="onSubmit">{{ $t('btn_save') }}</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>

<script>
import { ref, reactive, onMounted } from 'vue';
import rules from '@/Common/service/rule/validate-rules';

export default {
  name: 'LabelForm',
  props: {
    data: {
      type: Object,
      default() {
        return {
          Name: '',
          Describe: ''
        };
      }
    }
  },
  emits: ['onSubmit'],
  setup(props, { emit }) {
    const labelForm = ref(null);
    const tagRef = ref(null);
    const descRef = ref(null);

    const formData = reactive({ ...props.data });

    const rules = {
      Name: [rules.required, rules.name],
      Describe: [rules.textareaDescription]
    };

    const onSubmit = () => {
      labelForm.value.validate((valid) => {
        if (valid) {
          emit('onSubmit', formData);
        } else {
          labelForm.value.focusFirstField();
          return false;
        }
      });
    };

    const reset = () => {
      labelForm.value.resetFields();
    };

    const focusTag = () => {
      const inputDom = tagRef.value?.$el.querySelector('input');
      inputDom?.focus();
    };

    const focusDesc = () => {
      const inputDom = descRef.value?.$el.querySelector('textarea');
      inputDom?.focus();
    };

    return {
      labelForm,
      tagRef,
      descRef,
      formData,
      rules,
      onSubmit,
      reset,
      focusTag,
      focusDesc
    };
  }
};
</script>

网站公告

今日签到

点亮在社区的每一天
去签到