Skip to content

Commit

Permalink
fix(Form): fixed issue Tencent#3076
Browse files Browse the repository at this point in the history
  • Loading branch information
l123wx committed Oct 15, 2024
1 parent 581ddb5 commit 63698fb
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 116 deletions.
8 changes: 8 additions & 0 deletions src/form/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ const Form = forwardRefWithStatics(
form?.getInternalHooks?.(HOOK_MARK)?.flashQueue?.();
}, [form]);

// form 卸载时清空 floatingFormData
React.useEffect(
() => () => {
form.clearFloatingFormData();
},
[form],
);

function onResetHandler(e: React.FormEvent<HTMLFormElement>) {
[...formMapRef.current.values()].forEach((formItemRef) => {
formItemRef?.current.resetField();
Expand Down
24 changes: 10 additions & 14 deletions src/form/FormItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { forwardRef, ReactNode, useState, useImperativeHandle, useEffect,
import isObject from 'lodash/isObject';
import isString from 'lodash/isString';
import get from 'lodash/get';
import unset from 'lodash/unset';
import merge from 'lodash/merge';
import isFunction from 'lodash/isFunction';
import {
Expand All @@ -25,8 +26,8 @@ import { HOOK_MARK } from './hooks/useForm';
import { validate as validateModal, parseMessage } from './formModel';
import { useFormContext, useFormListContext } from './FormContext';
import useFormItemStyle from './hooks/useFormItemStyle';
import useFormItemInitialData, { ctrlKeyMap } from './hooks/useFormItemInitialData';
import { formItemDefaultProps } from './defaultProps';
import { ctrlKeyMap, getDefaultInitialData } from './useInitialData';
import { ValidateStatus } from './const';
import useDefaultProps from '../hooks/useDefaultProps';
import { useLocaleReceiver } from '../locale/LocalReceiver';
Expand Down Expand Up @@ -62,7 +63,6 @@ const FormItem = forwardRef<FormItemInstance, FormItemProps>((originalProps, ref
form,
colon,
layout,
initialData: FormContextInitialData,
requiredMark: requiredMarkFromContext,
labelAlign: labelAlignFromContext,
labelWidth: labelWidthFromContext,
Expand All @@ -76,12 +76,9 @@ const FormItem = forwardRef<FormItemInstance, FormItemProps>((originalProps, ref
onFormItemValueChange,
} = useFormContext();

const {
name: formListName,
rules: formListRules,
formListMapRef,
initialData: FormListInitialData,
} = useFormListContext();
const { name: formListName, rules: formListRules, formListMapRef } = useFormListContext();

const { getDefaultInitialData } = useFormItemInitialData();

const props = useDefaultProps<FormItemProps>(originalProps, formItemDefaultProps);

Expand Down Expand Up @@ -115,13 +112,15 @@ const FormItem = forwardRef<FormItemInstance, FormItemProps>((originalProps, ref
const [formValue, setFormValue] = useState(
getDefaultInitialData({
name,
formListName,
children,
initialData,
FormContextInitialData,
FormListInitialData,
}),
);
// 组件渲染后删除对应游离值
useEffect(() => {
const nameList = formListName ? [formListName, name].flat() : name;
unset(form.floatingFormData, nameList);
}, [form.floatingFormData, formListName, name]);

const formItemRef = useRef<FormItemInstance>(); // 当前 formItem 实例
const innerFormItemsRef = useRef([]);
Expand Down Expand Up @@ -326,11 +325,8 @@ const FormItem = forwardRef<FormItemInstance, FormItemProps>((originalProps, ref
if (resetType === 'initial') {
return getDefaultInitialData({
name,
formListName,
children,
initialData,
FormContextInitialData,
FormListInitialData,
});
}

Expand Down
84 changes: 84 additions & 0 deletions src/form/hooks/useFormItemInitialData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import React from 'react';
import get from 'lodash/get';

// 兼容特殊数据结构和受控 key
import Tree from '../../tree/Tree';
import Upload from '../../upload/upload';
import CheckTag from '../../tag/CheckTag';
import Checkbox from '../../checkbox/Checkbox';
import TagInput from '../../tag-input/TagInput';
import RangeInput from '../../range-input/RangeInput';
import Transfer from '../../transfer/Transfer';
import CheckboxGroup from '../../checkbox/CheckboxGroup';
import DateRangePicker from '../../date-picker/DateRangePicker';
import TimeRangePicker from '../../time-picker/TimeRangePicker';

import { useFormContext, useFormListContext } from '../FormContext';
import { FormItemProps } from '../FormItem';

// FormItem 子组件受控 key
export const ctrlKeyMap = new Map();
ctrlKeyMap.set(Checkbox, 'checked');
ctrlKeyMap.set(CheckTag, 'checked');
ctrlKeyMap.set(Upload, 'files');

// FormItem 默认数据类型
export const initialDataMap = new Map();
[Tree, Upload, Transfer, TagInput, RangeInput, CheckboxGroup, DateRangePicker, TimeRangePicker].forEach((component) => {
initialDataMap.set(component, []);
});
[Checkbox].forEach((component) => {
initialDataMap.set(component, false);
});

export default function useFormItemInitialData() {
const { form, initialData: formContextInitialData } = useFormContext();
const { floatingFormData } = form;

const { name: formListName, initialData: formListInitialData } = useFormListContext();

// 整理初始值 优先级:Form.initialData < FormList.initialData < FormItem.initialData < floatFormData
function getDefaultInitialData({
name,
children,
initialData,
}: {
name: FormItemProps['name'];
children: FormItemProps['children'];
initialData: FormItemProps['initialData'];
}) {
if (name && floatingFormData) {
const nameList = formListName ? [formListName, name].flat() : name;
const defaultInitialData = get(floatingFormData, nameList);
if (typeof defaultInitialData !== 'undefined') return defaultInitialData;
}

if (initialData) {
return initialData;
}

if (name && formListInitialData.length) {
const defaultInitialData = get(formListInitialData, name);
if (typeof defaultInitialData !== 'undefined') return defaultInitialData;
}

if (name && formContextInitialData) {
const defaultInitialData = get(formContextInitialData, name);
if (typeof defaultInitialData !== 'undefined') return defaultInitialData;
}

if (typeof children !== 'function') {
const childList = React.Children.toArray(children);
const lastChild = childList[childList.length - 1];
if (lastChild && React.isValidElement(lastChild)) {
// @ts-ignore
const isMultiple = lastChild?.props?.multiple;
return isMultiple ? [] : initialDataMap.get(lastChild.type);
}
}
}

return {
getDefaultInitialData,
};
}
25 changes: 22 additions & 3 deletions src/form/hooks/useInstance.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import isEmpty from 'lodash/isEmpty';
import isFunction from 'lodash/isFunction';
import merge from 'lodash/merge';
import get from 'lodash/get';
import set from 'lodash/set';
import { useRef } from 'react';
import type {
TdFormProps,
FormValidateResult,
Expand All @@ -10,7 +13,7 @@ import type {
NamePath,
} from '../type';
import useConfig from '../../hooks/useConfig';
import { getMapValue, travelMapFromObject, calcFieldValue } from '../utils';
import { getMapValue, objectToArray, travelMapFromObject, calcFieldValue } from '../utils';
import log from '../../_common/js/log';

// 检测是否需要校验 默认全量校验
Expand Down Expand Up @@ -42,6 +45,7 @@ function formatValidateResult(validateResultList) {

export default function useInstance(props: TdFormProps, formRef, formMapRef: React.MutableRefObject<Map<any, any>>) {
const { classPrefix } = useConfig();
const floatingFormDataRef = useRef<Record<any, any>>({});

const { scrollToFirstError, preventSubmitDefault = true, onSubmit, onReset } = props;

Expand Down Expand Up @@ -139,8 +143,16 @@ export default function useInstance(props: TdFormProps, formRef, formMapRef: Rea

// 对外方法,设置对应 formItem 的值
function setFieldsValue(fields = {}) {
travelMapFromObject(fields, formMapRef, (formItemRef, fieldValue) => {
formItemRef?.current?.setValue?.(fieldValue, fields);
const nameLists = objectToArray(fields);

nameLists.forEach((nameList) => {
const fieldValue = get(fields, nameList);
const formItemRef = formMapRef.current.get(nameList.length > 1 ? nameList : nameList[0]);
if (formItemRef?.current) {
formItemRef?.current?.setValue?.(fieldValue, fields);
} else {
set(floatingFormDataRef.current, nameList, fieldValue);
}
});
}

Expand Down Expand Up @@ -198,6 +210,11 @@ export default function useInstance(props: TdFormProps, formRef, formMapRef: Rea
});
}

// 对外方法,清空 floatingFormData
function clearFloatingFormData() {
floatingFormDataRef.current = {};
}

return {
submit,
reset,
Expand All @@ -211,5 +228,7 @@ export default function useInstance(props: TdFormProps, formRef, formMapRef: Rea
getFieldsValue,
currentElement: formRef.current,
getCurrentElement: () => formRef.current,
floatingFormData: floatingFormDataRef.current,
clearFloatingFormData,
};
}
8 changes: 8 additions & 0 deletions src/form/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,14 @@ export interface FormInstanceFunctions<FormData extends Data = Data> {
* 纯净的校验函数,仅返回校验结果,不对组件进行任何操作。泛型 `FormData` 表示表单数据 TS 类型。参数和返回值含义同 `validate` 方法
*/
validateOnly: (params?: Pick<FormValidateParams, 'fields' | 'trigger'>) => Promise<FormValidateResult<FormData>>;
/**
* 游离 formData,若调用 setFieldsValue 设置值对应的组件还没渲染,会暂存为游离值
*/
floatingFormData?: Record<any, any>;
/**
* 重置游离 formData
*/
clearFloatingFormData?: () => void;
}

export interface TdFormItemProps {
Expand Down
75 changes: 0 additions & 75 deletions src/form/useInitialData.ts

This file was deleted.

Loading

0 comments on commit 63698fb

Please sign in to comment.