vue-vben-admin icon indicating copy to clipboard operation
vue-vben-admin copied to clipboard

useFormItem.ts nextTick 方法导致dynamicRules第一次的值永远都不对

Open zuihou opened this issue 2 years ago • 17 comments

⚠️ 重要 ⚠️ 在进一步操作之前,请检查下列选项。如果您忽视此模板或者没有提供关键信息,您的 Issue 将直接被关闭

  • [ x ] 已阅读 文档.
  • [ x ] 确保您的代码已是最新或者所报告的 Bug 在最新版本中可以重现. (部分 Bug 可能已经在最近的代码中修复)
  • [ x ] 已在 Issues 中搜索了相关的关键词
  • [ x ] 不是 ant design vue 组件库的 Bug

描述 Bug

问题1: 使用 ApiSelect 、 ApiRadioGroup、原生的Select、 RadioGroup 等组件, rules的 trigger 设置为 'change', 每次改变选中的参数,会触发2次 validator。 1465

问题2: ApiSelect 或 ApiRadioGroup , 触发2次,因此表单校验(dynamicRules)就会执行2次,第一次执行dynamicRules函数,value和 model.xxx 的值为 上一次 选中的参数value (导致表单验证报错), 第二次执行dynamicRules函数,value和 model.xxx 的值是正确的。 (第二次执行dynamicRules函数传入的值正确,但并不会覆盖第一次的校验结果)

问题2已经在下方提供解决方案,但不知道为何2021年6月24日 作者为何原因加了 nextTick 方法, 我测试过:去掉nextTick后,问题2能解决。

伪代码:

field: 'resourceType',
component: 'ApiSelect',
// component: 'ApiRadioGroup',
componentProps: {
  api: asyncFindDictList,
    params: { type, excludes },
    resultField: 'data',
    showSearch: true,
    filterOption: (input: string, option: any) => {
      return option.label.toUpperCase().indexOf(input.toUpperCase()) >= 0;
    },
},
dynamicRules: ({ model }) => {
        return [
          {
            trigger: 'change',  // trigger: 'change' 每次改变select的参数,会触发2次 validator
            // trigger: 'blur',    // trigger: 'blur' 每次改变select的参数,只会触发一次 validator
            validator: async (_, value, ddd) => {
                  console.log(value);    // useRuleFormItem方法中,使用 nextTick 会导致第一次的value值不对 ,第二次才对
                  console.log(model.resourceType );
            }
            }
     ]
}

image

复现 Bug

  1. 在表单使用 ApiTree 或 ApiRadioGroup
  2. 然后写dynamicRules校验表单

如何解决?

// 正确的写法
      emit?.(changeEvent, value, ...(toRaw(unref(emitData)) || []));

// 错误的写法
      // nextTick(() => {
      //   emit?.(changeEvent, value, ...(toRaw(unref(emitData)) || []));
      // });

系统信息

  • 操作系统: window 和 mac
  • Node 版本: 16
  • 包管理器 (npm/yarn/pnpm) 及其版本: 6.23

zuihou avatar Jan 24 '22 12:01 zuihou

这么写还会出现别的问题

lzdjack avatar Jan 24 '22 12:01 lzdjack

加这个是为了emitdata能接受到

lzdjack avatar Jan 24 '22 12:01 lzdjack

可以看下pr1595,是不是和你描述的类似? 我去掉了useRuleFormItem,目前没有找到合适的方案,如果有请告知

lzdjack avatar Jan 24 '22 12:01 lzdjack

这么写还会出现别的问题

还会出现什么问题呀?

zuihou avatar Jan 24 '22 14:01 zuihou

可以看下pr1595,是不是和你描述的类似? 我去掉了useRuleFormItem,目前没有找到合适的方案,如果有请告知

按你pr的代码改动之后,确实能解决问题

zuihou avatar Jan 24 '22 14:01 zuihou

emitdata

这么写还会出现别的问题

还会出现什么问题呀?

明白了。不加 nextTick 会导致ApiSelect的onChange事件无法获取正确的 option 参数

onChange: function(value, option:Option/Array<Option>) {

}

zuihou avatar Jan 24 '22 14:01 zuihou

是的 为了避免破坏性更新,目前我只能这么改了,但是作者并没有合并,也没有改这里bug,可能这个方案还是不行

lzdjack avatar Jan 25 '22 00:01 lzdjack

经过讨论和研究,发现 问题1 触发2次验证是由于 BasicFrom.vue 中如下代码导致:

function setFormModel(key: string, value: any) {
  formModel[key] = value;
  const { validateTrigger } = unref(getBindValue);
  if (!validateTrigger || validateTrigger === 'change') {
    validateFields([key]).catch((_) => {});  // 这行代码导出触发2次表单验证
  }
}

不知 @mynetfan 为何加此代码?

zuihou avatar Jan 25 '22 15:01 zuihou

可以看下pr1595,是不是和你描述的类似? 我去掉了useRuleFormItem,目前没有找到合适的方案,如果有请告知

建议useRuleFormItem只保留emit(update:${key})事件, emit('change')放到组件的handleChange方法里 ; 建议去掉组件handleChange方法获得emitData,因为antd组件的emit('update:value')方法一般先于$emit('change');

januo avatar Jan 26 '22 03:01 januo

只能坐等各位解决------后端开发者

Perfect404 avatar Mar 25 '22 09:03 Perfect404

可以看下pr1595,是不是和你描述的类似? 我去掉了useRuleFormItem,目前没有找到合适的方案,如果有请告知

建议useRuleFormItem只保留emit(update:${key})事件, emit('change')放到组件的handleChange方法里 ; 建议去掉组件handleChange方法获得emitData,因为antd组件的emit('update:value')方法一般先于$emit('change');

按照您的说法调整代码后,测试了几种场景,感觉bug已消除 。修改如下:

  1. ApiSelect.vue 的 handleChange 方法修改为:
function handleChange(value, ...options) {
    emitData.value = options;
    emit('change', value, ...options);
  }
  1. useFormItem.ts 的 useRuleFormItem 方法修改为:
const state: any = computed({
    get() {
      return innerState.value;
    },
    set(value) {
      if (isEqual(value, defaultState.value)) return;

      innerState.value = value as T[keyof T];
      
// 注释  下面3行
      // nextTick(() => {
      //   emit?.(changeEvent, value, ...(toRaw(unref(emitData)) || []));
      // });
    },
  });

zuihou avatar Mar 29 '22 07:03 zuihou

可以看下pr1595,是不是和你描述的类似? 我去掉了useRuleFormItem,目前没有找到合适的方案,如果有请告知

建议useRuleFormItem只保留emit(update:${key})事件, emit('change')放到组件的handleChange方法里 ; 建议去掉组件handleChange方法获得emitData,因为antd组件的emit('update:value')方法一般先于$emit('change');

按照您的说法调整代码后,测试了几种场景,感觉bug已消除 。修改如下:

  1. ApiSelect.vue 的 handleChange 方法修改为:
function handleChange(value, ...options) {
    emitData.value = options;
    emit('change', value, ...options);
  }
  1. useFormItem.ts 的 useRuleFormItem 方法修改为:
const state: any = computed({
    get() {
      return innerState.value;
    },
    set(value) {
      if (isEqual(value, defaultState.value)) return;

      innerState.value = value as T[keyof T];
      
// 注释  下面3行
      // nextTick(() => {
      //   emit?.(changeEvent, value, ...(toRaw(unref(emitData)) || []));
      // });
    },
  });

@Perfect404 @lzdjack 感兴趣可以试试看

zuihou avatar Mar 29 '22 07:03 zuihou

好的

Perfect404 avatar Apr 06 '22 01:04 Perfect404

同ApiSelect目录下的几个ApiXXXX都需要修改,暂时好像没发现什么问题

Perfect404 avatar Apr 06 '22 01:04 Perfect404

@zuihou @lzdjack

Perfect404 avatar Apr 06 '22 01:04 Perfect404

可以看下pr1595,是不是和你描述的类似? 我去掉了useRuleFormItem,目前没有找到合适的方案,如果有请告知

建议useRuleFormItem只保留emit(update:${key})事件, emit('change')放到组件的handleChange方法里 ; 建议去掉组件handleChange方法获得emitData,因为antd组件的emit('update:value')方法一般先于$emit('change');

按照您的说法调整代码后,测试了几种场景,感觉bug已消除 。修改如下:

  1. ApiSelect.vue 的 handleChange 方法修改为:
function handleChange(value, ...options) {
    emitData.value = options;
    emit('change', value, ...options);
  }
  1. useFormItem.ts 的 useRuleFormItem 方法修改为:
const state: any = computed({
    get() {
      return innerState.value;
    },
    set(value) {
      if (isEqual(value, defaultState.value)) return;

      innerState.value = value as T[keyof T];
      
// 注释  下面3行
      // nextTick(() => {
      //   emit?.(changeEvent, value, ...(toRaw(unref(emitData)) || []));
      // });
    },
  });

貌似不需要修改useFormItem.ts也可以

iconFehu avatar Jun 09 '22 07:06 iconFehu

求更新!~

waney avatar Jul 20 '22 07:07 waney

已找到问题根源:由于 antd 的 select option 组件在点击时,会触发 form item 的 onFieldChange,该方法会直接调用 validateRules从而校验该字段,只需要给 form Item 添加 一个 props autoLink 即可解决该问题

const [register ]  = useForm({
    schemas:[
        {
            field: 'field1',
            label: 'field1',
            itemProps:{
                autoLink: false, // 添加该属性即可解决多触发一次 validator,value 为空值 
              },
        }
    ]
})

2022-12-30_14-13

123hyh avatar Dec 30 '22 06:12 123hyh

已找到问题根源:由于 antd 的 select option 组件在点击时,会触发 form item 的 onFieldChange,该方法会直接调用 validateRules从而校验该字段,只需要给 form Item 添加 一个 props autoLink 即可解决该问题

const [register ]  = useForm({
    schemas:[
        {
            field: 'field1',
            label: 'field1',
            itemProps:{
                autoLink: false, // 添加该属性即可解决多触发一次 validator,value 为空值 
              },
        }
    ]
})

2022-12-30_14-13

感谢大佬指导,终于找到问题根源了。

12914hh avatar Jun 07 '23 07:06 12914hh

感谢您提出的问题!在我维护这个项目之前,似乎已经有人修复了这个问题并且没有关闭这个issue。由于我无法复现该错误,我相信该问题已经解决。如果您仍然遇到类似的问题,请随时告诉我,我会尽力提供帮助!

wangjue666 avatar Oct 14 '23 09:10 wangjue666