diff --git a/src/form/Form.tsx b/src/form/Form.tsx index 4916c2ce18..3fe024c28a 100644 --- a/src/form/Form.tsx +++ b/src/form/Form.tsx @@ -3,8 +3,9 @@ import classNames from 'classnames'; import useConfig from '../hooks/useConfig'; import noop from '../_util/noop'; import forwardRefWithStatics from '../_util/forwardRefWithStatics'; -import type { TdFormProps, FormInstanceFunctions } from './type'; +import type { TdFormProps } from './type'; import useInstance from './hooks/useInstance'; +import useForm from './hooks/useForm'; import { StyledProps } from '../common'; import FormContext from './FormContext'; import FormItem from './FormItem'; @@ -15,10 +16,6 @@ export interface FormProps extends TdFormProps, StyledProps { children?: React.ReactNode; } -export interface FormRefInterface extends React.RefObject, FormInstanceFunctions { - currentElement: HTMLFormElement; -} - const Form = forwardRefWithStatics( (props: FormProps, ref) => { const { classPrefix, form: globalFormConfig } = useConfig(); @@ -26,18 +23,20 @@ const Form = forwardRefWithStatics( const { style, className, - labelWidth = '100px', + form, + labelWidth, statusIcon, - labelAlign = 'right', - layout = 'vertical', - colon = false, + labelAlign, + layout, + colon, + initialData, requiredMark = globalFormConfig.requiredMark, scrollToFirstError, - showErrorMessage = true, - resetType = 'empty', + showErrorMessage, + resetType, rules, errorMessage = globalFormConfig.errorMessage, - preventSubmitDefault = true, + preventSubmitDefault, disabled, children, onReset, @@ -50,39 +49,12 @@ const Form = forwardRefWithStatics( const formRef: React.RefObject = useRef(); const formMapRef = useRef(new Map()); // 收集所有 formItem 实例 + const formInstance = useInstance(props, formRef, formMapRef); - const { - submit, - reset, - getFieldValue, - getFieldsValue, - setFieldsValue, - setFields, - validate, - validateOnly, - clearValidate, - setValidateMessage, - } = useInstance(props, formRef, formMapRef); - - useImperativeHandle(ref as FormRefInterface, () => ({ - currentElement: formRef.current, - submit, - reset, - getFieldValue, - getFieldsValue, - setFieldsValue, - setFields, - validate, - validateOnly, - clearValidate, - setValidateMessage, - })); + useImperativeHandle(ref, () => formInstance); + form && Object.assign(form, { ...formInstance }); function onResetHandler(e: React.FormEvent) { - if (preventSubmitDefault) { - e.preventDefault?.(); - e.stopPropagation?.(); - } [...formMapRef.current.values()].forEach((formItemRef) => { formItemRef?.current.resetField(); }); @@ -90,7 +62,7 @@ const Form = forwardRefWithStatics( } function onFormItemValueChange(changedValue: Record) { - const allFields = getFieldsValue(true); + const allFields = formInstance.getFieldsValue(true); onValuesChange(changedValue, allFields); } @@ -110,6 +82,7 @@ const Form = forwardRefWithStatics( labelAlign, layout, colon, + initialData, requiredMark, errorMessage, showErrorMessage, @@ -125,7 +98,7 @@ const Form = forwardRefWithStatics( ref={formRef} style={style} className={formClass} - onSubmit={submit} + onSubmit={formInstance.submit} onReset={onResetHandler} onKeyDown={onKeyDownHandler} > @@ -134,7 +107,7 @@ const Form = forwardRefWithStatics( ); }, - { FormItem, FormList }, + { useForm, FormItem, FormList }, ); Form.displayName = 'Form'; diff --git a/src/form/FormContext.tsx b/src/form/FormContext.tsx index a5ca18cfce..5361a18ec5 100644 --- a/src/form/FormContext.tsx +++ b/src/form/FormContext.tsx @@ -8,6 +8,7 @@ const FormContext = React.createContext<{ labelAlign: TdFormProps['labelAlign']; layout: TdFormProps['layout']; colon: TdFormProps['colon']; + initialData: TdFormProps['initialData']; requiredMark: TdFormProps['requiredMark']; scrollToFirstError: TdFormProps['scrollToFirstError']; showErrorMessage: TdFormProps['showErrorMessage']; @@ -22,6 +23,7 @@ const FormContext = React.createContext<{ labelAlign: 'right', layout: 'vertical', colon: false, + initialData: {}, requiredMark: true, scrollToFirstError: undefined, showErrorMessage: true, diff --git a/src/form/FormItem.tsx b/src/form/FormItem.tsx index a9a62717f4..95d2d8c4c5 100644 --- a/src/form/FormItem.tsx +++ b/src/form/FormItem.tsx @@ -46,6 +46,7 @@ const FormItem = forwardRef((props, ref) => { const { colon, layout, + initialData: initialDataFromContext, requiredMark: requiredMarkFromContext, labelAlign: labelAlignFromContext, labelWidth: labelWidthFromContext, @@ -85,7 +86,15 @@ const FormItem = forwardRef((props, ref) => { const [verifyStatus, setVerifyStatus] = useState('validating'); const [resetValidating, setResetValidating] = useState(false); const [needResetField, setNeedResetField] = useState(false); - const [formValue, setFormValue] = useState(getDefaultInitialData(children, initialData)); + const [formValue, setFormValue] = useState( + getDefaultInitialData({ + name, + formListName, + children, + initialData, + initialDataFromContext, + }), + ); const currentFormItemRef = useRef(); // 当前 formItem 实例 const innerFormItemsRef = useRef([]); @@ -261,7 +270,13 @@ const FormItem = forwardRef((props, ref) => { function getResetValue(resetType: string): ValueType { if (resetType === 'initial') { - return getDefaultInitialData(children, initialData); + return getDefaultInitialData({ + name, + formListName, + children, + initialData, + initialDataFromContext, + }); } let emptyValue: ValueType = ''; diff --git a/src/form/__tests__/__snapshots__/form.test.tsx.snap b/src/form/__tests__/__snapshots__/form.test.tsx.snap index 0cf750ba9b..d0aee7557e 100644 --- a/src/form/__tests__/__snapshots__/form.test.tsx.snap +++ b/src/form/__tests__/__snapshots__/form.test.tsx.snap @@ -2930,6 +2930,7 @@ exports[`nested-data.jsx 1`] = ` >
@@ -2965,6 +2966,7 @@ exports[`nested-data.jsx 1`] = ` >
@@ -3137,11 +3139,11 @@ exports[`nested-data.jsx 1`] = ` class="t-date-picker" >
`; -exports[`size.jsx 1`] = ` - -
-
-
-
- - -
-
-
-
-
-
-
-
- -
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
- -
-
-
-
-
- -
-
-
-
- - -
-
-
-
-
-
- -
-
-
-
- - - -
-
-
-
-
-
-
- - -
-
-
-
-
-
- -`; - exports[`validate-complicated-data.jsx 1`] = `
{ console.log(e); @@ -19,14 +19,15 @@ export default function BaseForm() { }; const setMessage = () => { - formRef.current.setFields([ + console.log(form); + form.setFields([ { name: 'name', status: 'error', validateMessage: { type: 'error', message: '输入有误' } }, { name: 'birthday', status: 'warning', validateMessage: { type: 'warning', message: '时间有误' } }, ]); }; return ( - + diff --git a/src/form/_example/clear-validate.jsx b/src/form/_example/clear-validate.jsx index 3eb44a992e..c3fd304689 100644 --- a/src/form/_example/clear-validate.jsx +++ b/src/form/_example/clear-validate.jsx @@ -1,4 +1,4 @@ -import React, { useRef } from 'react'; +import React from 'react'; import { Form, Input, Checkbox, Button, MessagePlugin, Radio, Select } from 'tdesign-react'; const { FormItem } = Form; @@ -53,7 +53,7 @@ const options = [ ]; export default function BaseForm() { - const formRef = useRef(); + const [form] = Form.useForm(); const onSubmit = ({ validateResult, firstError }) => { if (validateResult === true) { @@ -69,17 +69,17 @@ export default function BaseForm() { }; const handleClear = () => { - formRef.current.clearValidate(); + form.clearValidate(); }; // 清除指定字段的校验结果 const clearFieldsValidateResult = () => { - formRef.current.clearValidate(['email', 'course', 'content.url']); + form.clearValidate(['email', 'course', 'content.url']); MessagePlugin.success('已清除邮箱、课程、个人网站等字段校验结果'); }; return ( - + diff --git a/src/form/_example/error-message.jsx b/src/form/_example/error-message.jsx index a7dee1b24c..e246ff9071 100644 --- a/src/form/_example/error-message.jsx +++ b/src/form/_example/error-message.jsx @@ -1,5 +1,4 @@ -/* eslint-disable no-template-curly-in-string */ -import React, { useRef, useState } from 'react'; +import React, { useState } from 'react'; import { Form, Input, Button, MessagePlugin, Radio, Select, Checkbox, Popup, Space } from 'tdesign-react'; const { FormItem } = Form; @@ -59,7 +58,7 @@ const rules = { }; export default function BaseForm() { - const formRef = useRef(); + const [form] = Form.useForm(); const [errorConfig, setErrorConfig] = useState('default'); const onSubmit = ({ validateResult, firstError }) => { @@ -76,7 +75,7 @@ export default function BaseForm() { }; const handleClear = () => { - formRef.current.clearValidate(); + form.clearValidate(); }; return ( @@ -98,7 +97,7 @@ export default function BaseForm() {
+ {(fields, { add, remove }) => ( <> diff --git a/src/form/_example/nested-data.jsx b/src/form/_example/nested-data.jsx index 5754a02a52..4d16d8c60f 100644 --- a/src/form/_example/nested-data.jsx +++ b/src/form/_example/nested-data.jsx @@ -4,7 +4,7 @@ import { Form, Input, Radio, Checkbox, Button, MessagePlugin, DatePicker } from const { FormItem } = Form; export default function BaseForm() { - const formRef = useRef(); + const [form] = Form.useForm(); const onSubmit = (e) => { console.log(e); @@ -12,15 +12,15 @@ export default function BaseForm() { MessagePlugin.info('提交成功'); } }; - + const setData = () => { - console.log('getFieldsValue all: ', formRef.current.getFieldsValue?.(true)); - console.log('getFieldsValue: ', formRef.current.getFieldsValue?.([['user', 'name']])); - console.log('getFieldValue: ', formRef.current.getFieldValue?.(['user', 'name'])); - formRef.current.setFieldsValue?.({ birthday: '2020-01-01' }); - formRef.current.setFieldsValue?.({ user: { gender: 'male' } }); - formRef.current.setFields?.([{ name: ['user', 'course'], value: ['la'] }]); - } + console.log('getFieldsValue all: ', form.getFieldsValue?.(true)); + console.log('getFieldsValue: ', form.getFieldsValue?.([['user', 'name']])); + console.log('getFieldValue: ', form.getFieldValue?.(['user', 'name'])); + form.setFieldsValue?.({ birthday: '2020-01-01' }); + form.setFieldsValue?.({ user: { gender: 'male' } }); + form.setFields?.([{ name: ['user', 'course'], value: ['la'] }]); + }; const onReset = (e) => { console.log(e); @@ -28,11 +28,25 @@ export default function BaseForm() { }; const onValuesChange = (value) => { - console.log(value) - } + console.log(value); + }; return ( - + diff --git a/src/form/_example/size.jsx b/src/form/_example/size.jsx deleted file mode 100644 index c6e654642e..0000000000 --- a/src/form/_example/size.jsx +++ /dev/null @@ -1,68 +0,0 @@ -import React, { useState } from 'react'; -import { Form, Input, Radio, Button, MessagePlugin, Switch, Space } from 'tdesign-react'; - -const { FormItem } = Form; - -export default function BaseForm() { - const [size, setSize] = useState('medium'); - - const onSubmit = ({ validateResult, firstError }) => { - if (validateResult === true) { - MessagePlugin.success('提交成功'); - } else { - console.log('Errors: ', validateResult); - MessagePlugin.warning(firstError); - } - }; - - const onReset = (e) => { - console.log(e); - MessagePlugin.info('重置成功'); - }; - - const courseOptions = [ - { label: '语文', value: '1' }, - { label: '数学', value: '2' }, - { label: '英语', value: '3' }, - ]; - - return ( - -
- setSize(value)} variant="default-filled"> - 中尺寸(默认) - 大尺寸 - -
- - - - - - - - - - - - - - - - - - - - - - - - - -
- ); -} diff --git a/src/form/_example/validate-message.jsx b/src/form/_example/validate-message.jsx index 9e195efb3b..376ac39a5d 100644 --- a/src/form/_example/validate-message.jsx +++ b/src/form/_example/validate-message.jsx @@ -1,4 +1,4 @@ -import React, { useRef, useEffect } from 'react'; +import React, { useEffect } from 'react'; import { Form, Input, Button, MessagePlugin } from 'tdesign-react'; const { FormItem } = Form; @@ -25,7 +25,7 @@ const rules = { }; export default function BaseForm() { - const formRef = useRef(); + const [form] = Form.useForm(); const onSubmit = ({ validateResult, firstError }) => { if (validateResult === true) { @@ -42,21 +42,21 @@ export default function BaseForm() { const handleValidateMessage = () => { MessagePlugin.success('设置表单校验信息提示成功'); - formRef.current.setValidateMessage(validateMessage); + form.setValidateMessage(validateMessage); }; const handleValidateOnly = () => { - formRef.current.validateOnly().then((result) => { + form.validateOnly().then((result) => { console.log('validateOnly: ', result) }); }; useEffect(() => { - formRef.current.setValidateMessage(validateMessage); + form.setValidateMessage(validateMessage); }, []); return ( -
+ diff --git a/src/form/_example/validator.jsx b/src/form/_example/validator.jsx index 1b147ab753..aaba831411 100644 --- a/src/form/_example/validator.jsx +++ b/src/form/_example/validator.jsx @@ -1,11 +1,11 @@ -import React, { useRef } from 'react'; +import React from 'react'; import { Form, Input, Radio, Checkbox, Button, MessagePlugin } from 'tdesign-react'; import debounce from 'lodash/debounce'; const { FormItem } = Form; export default function BaseForm() { - const formRef = useRef(); + const [form] = Form.useForm(); const onSubmit = (e) => { console.log(e); if (e.validateResult === true) { @@ -32,12 +32,12 @@ export default function BaseForm() { const handleChange = debounce((value) => { console.log('value', value); - formRef.current.validate({ fields: ['password'], trigger: 'blur' }); + form.validate({ fields: ['password'], trigger: 'blur' }); }, 500); return ( ` | N initialData | Object | - | \- | N labelAlign | String | right | options:left/right/top | N @@ -34,10 +35,11 @@ name | params | return | description className | String | - | 类名 | N style | Object | - | 样式,Typescript:`React.CSSProperties` | N clearValidate | `(fields?: Array)` | \- | \- +currentElement | \- | `HTMLFormElement` | \- getFieldValue | `(field: keyof FormData)` | `unknown` | \- getFieldsValue | \- | `getFieldsValue` | [see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/form/type.ts)。
`interface getFieldsValue{ (nameList: true): T; (nameList: string[]): Record;}`
reset | `(params?: FormResetParams)` | \- | [see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/form/type.ts)。
`interface FormResetParams { type?: 'initial' | 'empty'; fields?: Array }`
-setFields | `(fields: FieldData[])` | \- | Typescript:`(fields: FieldData[]) => void` `interface FieldData { name: string; value: unknown, status: string }`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/form/type.ts) +setFields | `(fields: FieldData[])` | \- | Typescript:`(fields: FieldData[]) => void` `interface FieldData { name: string; value?: unknown, status?: string, validateMessage?: { type?: string, message?: string } }`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/form/type.ts) setFieldsValue | `(field: Data)` | \- | \- setValidateMessage | `(message: FormValidateMessage)` | \- | [see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/form/type.ts)。
`type FormValidateMessage = { [field in keyof FormData]: FormItemValidateMessage[] }`

`interface FormItemValidateMessage { type: 'warning' | 'error'; message: string }`
submit | `(params?: { showErrorMessage?: boolean })` | \- | \- diff --git a/src/form/form.md b/src/form/form.md index d3c20a5f23..2882d55d4f 100644 --- a/src/form/form.md +++ b/src/form/form.md @@ -54,6 +54,7 @@ style | Object | - | 样式,TS 类型:`React.CSSProperties` | N colon | Boolean | false | 是否在表单标签字段右侧显示冒号 | N disabled | Boolean | undefined | 是否禁用整个表单 | N errorMessage | Object | - | 表单错误信息配置,示例:`{ idcard: '请输入正确的身份证号码', max: '字符长度不能超过 ${max}' }`。TS 类型:`FormErrorMessage` | N +form | Object | - | 经 `Form.useForm()` 创建的 form 控制实例。TS 类型:`FormInstanceFunctions` | N formControlledComponents | Array | - | 允许表单统一控制禁用状态的自定义组件名称列表。默认会有组件库的全部输入类组件:TInput、TInputNumber、TCascader、TSelect、TOption、TSwitch、TCheckbox、TCheckboxGroup、TRadio、TRadioGroup、TTreeSelect、TDatePicker、TTimePicker、TUpload、TTransfer、TSlider。对于自定义组件,组件内部需要包含可以控制表单禁用状态的变量 `formDisabled`。示例:`['CustomUpload', 'CustomInput']`。TS 类型:`Array` | N initialData | Object | - | 表单初始数据,重置时所需初始数据,优先级小于 FormItem 设置的 initialData | N labelAlign | String | right | 表单字段标签对齐方式:左对齐、右对齐、顶部对齐。可选项:left/right/top | N @@ -78,10 +79,11 @@ onValuesChange | Function | | TS 类型:`(changedValues: Record)` | \- | 清空校验结果。可使用 fields 指定清除部分字段的校验结果,fields 值为空则表示清除所有字段校验结果。清除邮箱校验结果示例:`clearValidate(['email'])` +currentElement | \- | `HTMLFormElement` | 获取 form dom 元素 getFieldValue | `(field: keyof FormData)` | `unknown` | 获取单个字段值 getFieldsValue | \- | `getFieldsValue` | 获取一组字段名对应的值,当调用 getFieldsValue(true) 时返回所有表单数据。[详细类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/form/type.ts)。
`interface getFieldsValue{ (nameList: true): T; (nameList: string[]): Record;}`
reset | `(params?: FormResetParams)` | \- | 重置表单,表单里面没有重置按钮`
"`; -exports[`ssr snapshot test renders ./src/form/_example/nested-data.jsx correctly 1`] = `"
"`; +exports[`ssr snapshot test renders ./src/form/_example/nested-data.jsx correctly 1`] = `"
"`; exports[`ssr snapshot test renders ./src/form/_example/reset.jsx correctly 1`] = `"
"`; -exports[`ssr snapshot test renders ./src/form/_example/size.jsx correctly 1`] = `"
"`; - exports[`ssr snapshot test renders ./src/form/_example/validate-complicated-data.jsx correctly 1`] = `"
学生1
学生2
"`; exports[`ssr snapshot test renders ./src/form/_example/validate-message.jsx correctly 1`] = `"
这是用户名字段帮助说明
一句话介绍自己
"`;