Skip to content

Form表单

提示

被设置了 name 属性的 Form.Item 包装的控件,表单控件会自动添加 value(或 valuePropName 指定的其他属性) onChange(或 trigger 指定的其他属性),数据同步将被 Form 接管, 这会导致以下结果:

你不再需要也不应该用 onChange 来做数据收集同步(你可以使用 Form 的 onValuesChange),但还是可以继续监听 onChange 事件。

你不能用控件的 value 或 defaultValue 等属性来设置表单域的值,默认值可以用 Form 里的 initialValues 来设置。

注意 initialValues 不能被 setState 动态更新,你需要用 setFieldsValue 来更新。

你不应该用 setState,可以使用 form.setFieldsValue 来动态改变表单值。

表单验证

相较vue中表单验证,antd中对输入框的验证全部放到了Form.Item中。 同时触发的事件诸如onBlur,onChange Form.Item中,(通过validateTrigger来指定)

对于自定义校验validator函数:它会在每次事件触发的时候执行,这样就会出现如果设置了

text
rules = {
	[
		{
			required: true,
			message: '手机号不能为空'
		},
		{
			required: true,
				validator:(rule, value) => {
				if (value) {
					let reg = /^1(3|4|5|6|7|8|9)\d{9}$/g;
					if (reg.test(value)) {
						return Promise.resolve()
					} else {
						return Promise.reject('手机号格式错误')
					}
				} else {
					return Promise.reject('请输入正确的手机号')
				}
			}
		}
	]
}

两个验证规则。原本的目的是在没有输入任何值时只提示“手机号不能为空”,结果同时出现“请输入正确的手机号”“手机号格式错误”两个报错。 也就是validator优先级最高。所以解决办法有两种: (1)所有的验证放在validator中包括为空的情况 (2)不要validator函数,而是把为空和验证规则拆开,如下:

jsx
<Form.Item
	name="account"
	validateTrigger="onBlur"
	rules={
		[
			{
				required: true,
				message: '手机号不能为空'
			},
			{
				pattern: /^1(3|4|5|6|7|8|9)\d{9}$/g,
				message: "请输入正确的手机号"
			},

		]
	}
>
	<Input size="large" max={11} value={account} allowClear prefix={<IconFont type="icon-zhanghao" />} />
</Form.Item>

表单的自定义验证的两种方式:

(1) rules里直接通过数组配置规则,如下:

jsx
 <Form.Item
	name="account"
	validateTrigger="onBlur"
	rules={
		[
			{
				required: true,
				message: '手机号不能为空'
			},
			{
				pattern: /^1(3|4|5|6|7|8|9)\d{9}$/g,
				message: "请输入正确的手机号"
			},

		]
	}
>
	<Input size="large" max={11} value={account} allowClear prefix={<IconFont type="icon-zhanghao" />} />
</Form.Item>

(2) 自定义验证函数validator

注意返回的是一个promise. resove或者reject以后的值:验证通过直接通过promise.resolve()值出来。 验证不通过则需要通过promise.reject()抛出错误原因

jsx
  const validatorCatagory = (rule, value, type) => {
	console.log("value", value)
	console.log("type", type)
	let str = type === "cassify" ? "类目" : "类目编号"
	if (!value) {
		return Promise.reject(`${str}不能为空`)
	} else {
		console.log("value:", value)
		if ((/[a-zA-Z0-9]/.test(value)) && (value.length > 0 && value.length <= 60)) {
			setCatagoryNum(value)
			return Promise.resolve(value)
		} else {
			return Promise.reject(`${str}不能含有特殊字符并且不能大于60个字符`)
		}
	}
}


<Form.Item
	label="类目名"
	name="type"
	validateTrigger='onBlur'
	required
	rules={[
		{ validator: (rule, value,) => validatorCatagory(rule, value, "cassify") },
	]}>
	<Input style={{ width: 320 }} value={catagoryName} onChange={e => updataName(e)} />
</Form.Item>

表单提交

按钮提交

有时候我们表单提交不一定是在表单的submit按钮下。 可能是在其他区域点击触发表单的整体验证。 可以通过 两种方式来实现:

方式一:直接通过useForm获取form的引用

jsx
import { Modal, Form, } from 'antd';

const [form] = Form.useForm()

function ComName() {
	cosnt
	validata = () => {
		form.setFieldsValue({
			catagoryName: data.name,

		})
	}
	return (
		<div>
			<Form
				labelCol={{ span: 6 }}
				wrapperCol={{ span: 18 }}
				form={form}
				initialValues={formInitialValues}
			>
				<Form.Item
					label="类目名"
					name="catagoryName"
					validateTrigger='onBlur'
					required
				>
					<Input style={{ width: 320 }} value={catagoryName} />
				</Form.Item>>
			</Form>
			<Button onClick={validata}>提交</Button>
		</div>
	)
}

方式二:通过useRef获取form的引用

jsx
import { useRef, useEffect } from 'react';

function EditCatagory(props) {
	const fromRef = useRef()
	const submit = () => {
		fromRef.current.validateFields().then(res => {

		}).catch(_ => {

		})
	}

	return (
		<div>
			<Form
				labelCol={{ span: 6 }}
				wrapperCol={{ span: 18 }}
				form={form}
				ref={fromRef}
				initialValues={formInitialValues}
			>
				xxx...
			</Form>
			<Button onClick={submit}>提交</Button>
		</div>
	)
}

如果表单块之间没有强关联关系,可以拆分出来,分别校验,通过formRef取值,一起提交。

数据处理

用form 表单域获取数据时,如果表单提交的数据不需要全部传入后台,可以所需的数据解构出来,作为参数传入,以保证代码的高质量。 const { title = '', source = '' ,labelName = '' } = values;

设置表单默认值问题

如下图: 是否继承属性设置这个选项的默认值根据第一个红色框框的类目位置来决定。 它是一个数组。 如果数组长度>1,则"是否继承属性设置“ 的值 默认勾选为“自定义设置”,否则则勾选"继承上级属性设置“。 因为有这层逻辑,所以一开始打算在useEffect里动态设置其defaultValue。开始的代码如下:

jsx

function CreateCatagory(props) {

	const attrSettingDialogRef = useRef()
	const [inheritPerporty, setInheritPerporty] = useState(1)
	const [setttingVal, setSetttingVal] = useState(1)

	const [form] = Form.useForm()
	const formRef = React.createRef();

	useEffect(() => {
		let val = catagoryPositions.length > 1 ? 2 : 1;
		setInheritPerporty(val)
	}, [])

	return (
		<Form
			labelCol={{ span: 6 }}
			wrapperCol={{ span: 18 }}
			form={form}
			ref={formRef}
			initialValues={formInitialValues}
		>
			< Form.Item
				label="类目名"
				name="type"
				rules={[{ required: true, message: '所属类型不能为空' }]}>
				< Input
					style={{ width: 320 }}
				/>
			</Form.Item>
			<Form.Item label="类目编号" name="code" rules={[{ required: true, message: '所属类型不能为空' }]}>
				<Input style={{ width: 320 }} />
			</Form.Item>
			<Form.Item label="类目位置" name="position" rules={[{ required: true, message: '属性名不能为空' }]}>
				<Input style={{ width: 320 }} disabled defaultValue={catagoryPositions.join("/")} />
			</Form.Item>
			<Form.Item label="是否为末级类目" name="control" rules={[{ required: true, message: '属性控件不能为空' }]}>
				<Radio.Group onChange={onChange} value={isLastCatagoryVal}>
					<Radio value={1}></Radio>
					<Radio value={2}></Radio>
				</Radio.Group>
			</Form.Item>
			<Form.Item label="是否继承属性设置" name="inherit" rules={[{ required: true, message: '属性控件不能为空' }]}>
				<Radio.Group onChange={inheritPerportyFn} defaulteValue={inheritPerporty} value={setttingVal}>
					<Radio value={1}>继承上级属性设置</Radio>
					<Radio value={2}>自定义设置</Radio>
				</Radio.Group>
			</Form.Item>
		</Form>
	)
}

export default CreateCatagory

结果报错: 顺着报错,initialValues看了下antd的官网有这样一段描述

  • 你不再需要也不应该用 onChange 来做数据收集同步(你可以使用 Form 的 onValuesChange),但还是可以继续监听 onChange 事件。
  • 你不能用控件的 value 或 defaultValue 等属性来设置表单域的值,默认值可以用 Form 里的 initialValues 来设置。
  • 注意initialValues 不能被 setState 动态更新,你需要用 setFieldsValue 来更新。
  • 你不应该用 setState,可以使用 form.setFieldsValue 来动态改变表单值 所以做了一个尝试:代码改动如下:
jsx
import { Modal, Form, Input, Button, Space, Select, Radio, Tabs, Checkbox } from 'antd';
import { useState, useRef, useEffect } from 'react';
import "./index.less"
import Iconfont from 'components/iconfont';


const list = [
	{ id: "0001", name: "a属性" },
	{ id: "0002", name: "b属性" },
	{ id: "0003", name: "c属性" },
	{ id: "0004", name: "d属性" },
	{ id: "0005", name: "e属性" },
	{ id: "0006", name: "f属性" },
]
// const catagoryPositions=["一级类目","二级类目"].join("/")
const catagoryPositions = ["一级类目", "二级类目"]

function CreateCatagory(props) {

	const attrSettingDialogRef = useRef()
	const [isLastCatagoryVal, setIsLastCatagoryVal] = useState(1)
	const [setttingVal, setSetttingVal] = useState(1)
	const [isShowPeroptyRow, setIsShowPeroptyRow] = useState(true)

	const formInitialValues = ({
		inherit: 1
	})

	const [form] = Form.useForm()
	const formRef = React.createRef();


	useEffect(() => {
		let val = catagoryPositions.length > 1 ? 2 : 1;
		formRef.current.setFieldsValue({
			inherit: val
		})
	}, [])


	const onChange = () => {

	}
	const changeTab = () => {

	}
	const setAttrValue = () => {
		attrSettingDialogRef.current.showModelRef()

	}
	const confirmFn = (data) => {
		console.log("data:", data)
	}
	const inheritPerportyFn = (e) => {
		console.log("val:", e.target.value)
		e.target.value === 2 ? setIsShowPeroptyRow(true) : setIsShowPeroptyRow(false)
	}
	return (
		<div className="add-attr-page">
			<div className="head-title">
				<Iconfont type="icon-down"></Iconfont>
				基础属性
			</div>
			<div className="basic-info">
				<Form
					labelCol={{ span: 6 }}
					wrapperCol={{ span: 18 }}
					form={form}
					ref={formRef}
					initialValues={formInitialValues}
				>
					<Form.Item label="类目名" name="type" rules={[{ required: true, message: '所属类型不能为空' }]}>
						<Input style={{ width: 320 }} />
					</Form.Item>
					<Form.Item label="类目编号" name="code" rules={[{ required: true, message: '所属类型不能为空' }]}>
						<Input style={{ width: 320 }} />
					</Form.Item>
					<Form.Item label="类目位置" name="position" rules={[{ required: true, message: '属性名不能为空' }]}>
						<Input style={{ width: 320 }} disabled defaultValue={catagoryPositions.join("/")} />
					</Form.Item>
					<Form.Item label="是否为末级类目" name="control" rules={[{ required: true, message: '属性控件不能为空' }]}>
						<Radio.Group onChange={onChange} value={isLastCatagoryVal}>
							<Radio value={1}></Radio>
							<Radio value={2}></Radio>
						</Radio.Group>
					</Form.Item>
					<Form.Item label="是否继承属性设置" name="inherit" rules={[{ required: true, message: '属性控件不能为空' }]}>
						<Radio.Group onChange={inheritPerportyFn} value={setttingVal}>
							<Radio value={1}>继承上级属性设置</Radio>
							<Radio value={2}>自定义设置</Radio>
						</Radio.Group>
					</Form.Item>
				</Form>
			</div>
			{
				isShowPeroptyRow ?
					<div className="perptoty-setting-box">
						<div className="head-title">
							<Iconfont type="icon-down"></Iconfont> 属性设置
						</div>
						<div className="attr-setting">
							<div className="tab-wrap">
								<div className='basic-attrs atts-item'>
									<div className='name'>定额基础属性:</div>
									<div className='attr-setting-container'>
										<Space size={20}>

											{
												list.map(item => (
													<Space size="large">
														<span>
																<Checkbox onChange={onChange}>{item.name}</Checkbox>
																<Iconfont type="icon-edit"
																					onClick={setAttrValue}></Iconfont>
														</span>
														<span>
																<Checkbox onChange={onChange}>必填</Checkbox> 
														</span>
													</Space>
												))
											}
										</Space>
									</div>
								</div>
								<div className='basic-attrs atts-item'>
									<div className='name'>主材属性:</div>
									<div className='attr-setting-container'>
										<Space>
											{
												list.map(item => (
													<Space size="large">
														<span>
																<Checkbox onChange={onChange}>{item.name}</Checkbox>
																<Iconfont type="icon-edit"
																					onClick={setAttrValue}></Iconfont>
														</span>
														<span>
																<Checkbox onChange={onChange}>必填</Checkbox> 
														</span>
													</Space>
												))
											}
										</Space>
									</div>
								</div>
								<div className='basic-attrs atts-item'>
									<div className='name'>辅材属性:</div>
									<div className='attr-setting-container'>
										<Space>
											{
												list.map(item => (
													<Space size="large">
														<span>
																<Checkbox onChange={onChange}>{item.name}</Checkbox>
																<Iconfont type="icon-edit"
																					onClick={setAttrValue}></Iconfont>
														</span>
														<span>
																<Checkbox onChange={onChange}>必填</Checkbox> 
														</span>
													</Space>
												))
											}
										</Space>
									</div>
								</div>
							</div>
							<div className="btns">
								<Space>
									<Button type="">取消</Button>
									<Button type="primary">确认</Button>
								</Space>
							</div>
						</div>
					</div> : ""
			}
		</div>
	)
}

export default CreateCatagory

通过表单本身去进行setFieldsValue操作。把defaultValue的逻辑改为表单的操作。终于可以正常显示。 注意改动的地方: (1)form表单本身绑定initialValues={formInitialValues} (2)定义一个初始值:

jsx
const formInitialValues = ({
	inherit: 1,
	isLastCatagory: 1
})

(3) useEffect里通过formRef.current.setFieldsValue({inherit: val})来设置默认值。

jsx
  useEffect(() => {
	let val = catagoryPositions.length > 1 ? 2 : 1;
	formRef.current.setFieldsValue({
		inherit: val,
		isLastCatagory: val
	})
}, [])

这里补充一点:

在antd中,对form的处理有两种思路 一种是通过把表单元素作为受控组件。也就是我们通过形如

jsx
<Input value={aaa} onChange={e => change(e)}>

我们通过给表单元素的value绑定某个值然后通过change事件更改其值。来驱动数据的更改

第二种思路则是最常见的一种:通过form实例的相关方法来驱动:

如下:

jsx
import { Form, Input, Button } from "antd"

const FormDemo = () => {

	const [form] = Form.useForm();

	let initValus = {
		username: "",
		password: ""
	}

	const view = () => {
		//    let res= form.getFieldsValue()
		let res = form.getFieldValue("username")
		console.log("res:", res)
	}

	return (
		<div style={{ width: "520px" }}>
			< Form initialValues={initValus} form={form}>
				<Form.Item name="username" label="用户名">
					<Input placeholder-="请输入姓名"></Input>
				</Form.Item>
				<Form.Item name="password" label="密码">
					<Input placeholder-="请输入密码"></Input>
				</Form.Item>
			</Form>
			<Button onClick={view}>查看</Button>
		</div>
	)
}

export default FormDemo

form实例的getFieldsValue()能获取到每个Form.Item的字段的值;

form实例的getFieldValue(“username”)能获取某个字段,比如usernameForm.Item的字段的值;

form实例的setFieldsValue()能设置某个字段或者所有字段的值。

此二种方法用其一即可

Form表单里面Item默认值的回填问题

例如:

使用 initialValue 属性回填信息,但是如果这个值开始没有

使用三元运算符写为action?.DemoAddress ? action?.DemoAddress:null,页面默认值就会出现不合法的值,后面的空值要写为undefined。

jsx

<Item
	label="演示环境"
	name="demoAddress"
	labelAlign="left"
	initialValue={action?.DemoAddress ? action?.DemoAddress : undefined}
	rules={[{ required: true, message: '请选择演示环境!' }]}
/>

在表单控件中如何传递参数?

实现方式:箭头函数传参,注意箭头函数第一个参数只能是e,第二个函数里传对应的数据

text
 const selectedItem = (e, data) => {
	console.log("e:", e)
	console.log("data:", data)
}

{
	list.map(checkboxItem => (<Checkbox
		onChange={(e) => selectedItem(e, checkboxItem)}
		checked={checkboxItem.isChecked}
	>
		{checkboxItem.label}
	</Checkbox>))
}

Form表单结合Modal框使用

Form表单结合Modal框使用时,当关闭Modal框时,form表单的内容需要清空,使用form.resetFields() 清空即可,但当时组件写完没有起作用,所以经过阅读文档,查阅到另一种方法:

form表单的input框中,填入默认值,使用initialValue

但是使用后发现,默认值不会随着数据的变化而变化,第一次刷新后不会变化,查阅官方文档,得知:

所以当数据发生变化时,可以使用form.setFieldsValue来动态更新input框的默认值。

关于antd中的Form组件设置了initialValues属性后,如何在state或者props中的属性变化后,重新渲染修改Form中的数据显示的三种解决办法

https://article.juejin.cn/post/7143031321554583565?from=search-suggest

确认提交和取消提交时关于rules验证问题:

在点击修改时,在input框中填入内容,触发rules的动态验证,所以导致取消提交时,rules的动态验证仍然存在,解决方法:

在点击取消修改时,可以停止校验即可不在出现rules校验内容。

js
form.setFields([
	{
		name: 'phone', value: ' ', errors: null
	}

])

Input

input的只读模式

<Input readOnly />

注意:是readOnly,readonly或readonly=”readonly”无效

suffix属性

(1)suffix可以显示在表单控件的尾部,可以帮助显示输入的字数

(2)suffix属性可以和getFieldValue('')配和,根据属性名来识别当前正在输入的表单getFieldValue('name').length 就是当前输入的表单的长度,也就是字数

text
<Input suffix={getFieldValue('name').length} maxLength="30" />

组件取值问题

(1)有的组件是直接取值,从value中取值,有的组件是从 e.target.value中取值

(2) select组件是直接取value的值,input , textarea等文本输入框都是从e.target.value中取值

text
val => this.hisIdChange(val)} >
 onChange={(e) => {	
   this.valye =  e.target.value
  }}
/>

antd的form 的input输入框怎么限制不能输入空格

AntDesign中的Form表单其实提供了非常丰富的校验方式,有两种方式解决input不能输入空格的问题(具体看自己的需求)

text
<Form.Item label="名字">
  {getFieldDecorator('name', {
    rules: [
        {
         required: true,
         message: '请输入名字',
        },
   // 方式一:正则匹配(提示错误,这种会在提交表单时给提示,阻止表单提交)
       { pattern: /^[^\s]*$/, message: '禁止输入空格',}
],
 
  // 方式二:粗暴点<Input>不允许输入空格(其实是将e.tartget.value转成控件自己的值)
  // 这个方法的用途非常强大,还可以结合upload做一些文件上传之后的回调处理
      getValueFromEvent: (event) => {
         return event.target.value.replace(/\s+/g,"")
        },
})
(<Input size="large" placeholder="请输入名字" maxLength={20} />)}
</Form.Item>

InputNumber

precision 的值为整数,表示小数点后保留几位小数

Ant Design 中的 InputNumber 组件可以设置 precision 属性来控制输入数字的精度。precision 的值为整数,表示小数点后保留几位小数。例如: <InputNumber precision={2} />

这将限制用户只能输入最多两位小数的数字。

默认情况下,precision 值为 undefined,表示不限制小数位数。

另外还有min,max 可以设置输入的最大最小值。

antd inputNumber 如何设置,实现 输入范围2.00 - 9.00,步长为0.1

在Ant Design中,InputNumber组件允许你设置一个输入框,专门用于接收数值输入。要实现你提出的范围为2.00至9.00,步长为0.1的功能,你可以使用min、max和step属性。 以下是实现这个需求的代码示例:

jsx
import React from 'react';
import { InputNumber } from 'antd';

const InputNumberExample = () => (
	<InputNumber
		min={2}
		max={9}
		step={0.1}
		formatter={value => `${value}`.replace(/\d*\.?0*$/, '')}
		parser={value => value.replace(/\$\s?|(?!\.\d{1,2})\d{1,2}(?=(\d{3})+(?!\d))|\.\d{1,2}$/g, '$1')}
		style={{ width: 120 }}
	/>
);

export default InputNumberExample;

这段代码中,formatter和parser属性用于格式化和解析用户的输入,以确保数值正确显示和解析。 style属性用于设置输入框的样式宽度。用户将只能在2.00至9.00之间输入,并且每次可以增加或减少0.1。

比如我需要实现即使用户输入的是2.04,也会改成2.00 ,若输入2.35,则改成2.40,该如何书写

要实现这样的需求,即使用户输入了像2.04这样的值,它会自动被四舍五入到最近的0.1(也就是2.00),而2.35会被向上取整到2.40,你需要自定义onChange 处理函数来处理这些逻辑。

InputNumber组件中,onChange属性接收一个回调函数,这个函数会在用户输入或修改值时调用,并传入当前的值作为参数。你可以在这个函数中实现你的自定义逻辑。

下面是一个简单的实现示例:

jsx
import React, { useState } from 'react';
import { InputNumber } from 'antd';

const CustomInputNumber = () => {
	const [value, setValue] = useState(null);

	const handleChange = (newValue) => {
		// 四舍五入到最近的0.1  
		const roundedValue = Math.round(newValue * 10) / 10;

		// 设置状态,以触发组件重新渲染  
		setValue(roundedValue);
	};

	return (
		<InputNumber
			min={2}
			max={9}
			step={0.1}
			value={value}
			onChange={handleChange}
			style={{ width: 120 }}
		/>
	);
};

export default CustomInputNumber;

在这个示例中,我们使用了React的useState钩子来管理InputNumber组件的值。handleChange 函数会在用户输入时调用,并使用Math.round函数将值四舍五入到最近的0.1。然后,我们使用setValue 来更新状态,这会触发组件的重新渲染,并显示四舍五入后的值。

现在,无论用户输入什么值,只要它在2.00到9.00的范围内,它都会被四舍五入到最近的0.1,并且显示在InputNumber组件中。

上述代码会保留两位小数吗?

是的,上述代码会保留两位小数,因为InputNumber组件默认会保留小数点后两位。当用户输入或修改值时,它会自动格式化为保留两位小数的形式。 不过,如果你想要确保即使输入的是2.0或2这样的值也显示为2.00,你可以在formatter函数中添加一些逻辑来确保总是显示两位小数。

以下是更新后的formatter和parser函数,以确保输入值始终显示为两位小数:

jsx
import React from 'react';
import { InputNumber } from 'antd';

const InputNumberExample = () => (
	<InputNumber
		min={2}
		max={9}
		step={0.1}
		formatter={(value) => `${parseFloat(value).toFixed(2)}`}
		parser={(value) => parseFloat(value)}
		style={{ width: 120 }}
	/>
);

export default InputNumberExample;

在这个例子中,formatter函数使用toFixed(2) 来确保值始终有两位小数。如果用户输入2.04,它会被格式化为2.04;如果输入2.35,它也会被格式化为2.35。同样,如果用户输入2或2.0,它们会被格式化为2.00。

parser函数将格式化后的字符串转换回数字,以便在内部使用。这里使用parseFloat来解析值,它会自动忽略多余的零和小数点。

这样,用户看到的值始终会保留两位小数,而组件内部处理的值也将是正确解析的数字。

若用户输入的是空值呢,不会报错吧?

在Ant Design的InputNumber组件中,如果用户输入空值(即没有输入任何数值),通常不会报错。InputNumber组件会将其视为null或undefined,这取决于你如何设置组件的value和onChange属性。

如果你使用InputNumber组件的受控模式(即你通过value属性控制组件的值,并监听onChange事件来更新状态),你可能需要处理空值的情况。当用户清除输入框中的内容时,onChange事件会被触发,并且newValue将会是undefined或空字符串。你可以在你的onChange处理函数中检查这个值,并相应地更新你的状态。

下面是一个处理空值的onChange函数的例子:

jsx
import React, { useState } from 'react';
import { InputNumber } from 'antd';

const CustomInputNumber = () => {
	const [value, setValue] = useState(null);

	const handleChange = (newValue) => {
		// 当用户清除输入框内容时,newValue 将会是 undefined
		if (newValue === undefined || newValue === '') {
			setValue(null); // 你可以设置为你想要的默认值,比如 null
		} else {
			// 确保值是有效的数字,并且在范围内
			const parsedValue = parseFloat(newValue);
			if (parsedValue >= 2 && parsedValue <= 9) {
				setValue(parsedValue);
			}
		}
	};

	return (
		<InputNumber
			min={2}
			max={9}
			step={0.1}
			value={value}
			onChange={handleChange}
			style={{ width: 120 }}
		/>
	);
};

export default CustomInputNumber;

在这个例子中,如果用户尝试输入空值,handleChange函数会检查newValue,如果它是undefined或空字符串,就会将状态value设置为null。如果用户输入了有效的数字,并且这个数字在2.00到9.00的范围内,setValue将会被调用以更新状态。

请注意,由于InputNumber组件的value属性接受的是数字,所以当用户输入非数字字符时,组件会自动将其视为无效输入,并不会触发onChange事件。这意味着你不需要担心非数字输入导致的错误。

Select

select的模糊搜索以及多选

text
<Form form={form}>
    <Form.Item label='标签' name="TagName" labelAlign="left" >
    <Select 
      mode="multiple"  / /支持多选
      showSearch
      allowClear
      filterOption={(input, option: any) => option?.props.children?.indexOf(input) >= 0}   //模糊搜索
      getPopupContainer={triggerNode => triggerNode.parentNode}>
    {handlerUser.map((res) => (
        <Select.Option key={res?.id} value={res?.TagName}> //支持鼠标点到select框后,将后端数据展示到下拉框
          {res?.TagName}
        </Select.Option>
    ))}
    </Select >
</Form.Item>
</Form>

filterOption 筛选符合条件的选项

showSearch 配置是否可搜索 筛选包含在options.props.value或者options.props.children之中的input的值, 全部转化为小写之后再进行比对,不区分大小写,剔除不符合条件的选项

text
 filterOption={
 (input, options) => {
  return (
   options.props.value.toLowerCase().indexOf(input.toLowerCase()) >= 0 ||
   options.props.children.indexOf(input.toLowerCase()) >= 0
 )}
 }

antd Select组件 filterOption使用踩坑报(options.props.children.toLower.Case() is not funcrion)

https://blog.csdn.net/weixin_44058725/article/details/110520032?spm=1001.2014.3001.5502 一旦Option标签里显示值的前后有空格则option.props.children的类型则会成为array类型,这时如果使用了string的方法则会报错导致页面崩溃 还可能出现 TypeErro:Cannot resd property ‘toLowerCase’ of null at filterOption . 需要判断 值不为空的情况 这时就需要 加个判断 再使用 toLowerCase

text
 filterOption = {(input, option) => 
       let val=(Array.isArray(option.children) ? option.children.join('') : option.children);
       return val?val.toLowerCase().indexOf(input.toLowerCase()) >= 0:null
    }

select的默认值 和赋值 时,需要考虑类型必须全等才可以赋值上

Error: must set key for children

在使用antd-design中的select的组件时候,报这样的错误。

原因:在select中设置了多选mode = ‘multiple’,并把initialValue或value的值设为了[”];

解决方法:initialValue或value值设为空[],或不为空的字符串[“xxx”]; 事实上 tree,treeSelect 都是如此

DatePicker

选不上日期是可能是默认值的影响

DatePicker设置默认值时value不能为空,否则叉掉时间后数据格式会显示不正确

可以设置为null,来避免这个问题。 value={ beginTime ? beginTime : null }

DatePicker的默认值,使用时间戳形式

jsx
<Item
	label="预约开始时间:"
	name="startTime"
	labelAlign="left"
	initialValue={action?.StartTime ? moment(action?.StartTime) : ''}
	rules={[{ required: true, message: '请选择预约开始时间!' }]}
>

	<DatePicker showTime style={{ width: '100%' }} placeholder="请选择预约开始时间!" />
</Item>

<DatePicker defaultValue={moment('2015/01', monthFormat)} format={monthFormat} picker="month" />

DatePickers的placeholder是英文的,自定义即可

html

<DatePicker.RangePicker placeholder={['开始日期', '结束日期']} />

时间转化:

后端返回时间类型是:2022-07-27T14:24:01+08:00,前端进行转化:

1)首先写一个转换函数:

js
// 时间戳转换时间格式
export default function formatDate(time) {
	Date.parse(time)
	const date = new Date(time);
	const YY = `${date.getFullYear()}-`;
	const MM = `${date.getMonth() + 1 < 10 ? `0${date.getMonth() + 1}` : date.getMonth() + 1}-`;
	const DD = (date.getDate() < 10 ? `0${date.getDate()}` : date.getDate());
	const hh = `${date.getHours() < 10 ? `0${date.getHours()}` : date.getHours()}:`;
	const mm = `${date.getMinutes() < 10 ? `0${date.getMinutes()}` : date.getMinutes()}:`;
	const ss = (date.getSeconds() < 10 ? `0${date.getSeconds()}` : date.getSeconds());
	return `${YY + MM + DD} ${hh}${mm}${ss}`;
}
text
import formatDate from '@/hooks/setTimeFormat'

formatDate(2022-07-27T14:24:01+08:00)  即可转换

不带毫秒数的时间戳
format前要先unix为标准格式, moment.unix(int类型)。
多个时间方法调用:

ts
const formatDate = (timeTs, formatter = 'YYYY-MM-DD HH:mm:ss') => (
	!timeTs ? '' : moment.unix(parseInt(timeTs, 10)).format(formatter)
);
const planEndTime = formatDate(stateSit.planEndTime);

TextArea

样式以及固定高度设置 固定高度使用: row={5}

text

<TextArea style={{ width: '100%', resize: 'none' }} rows={5} />

checkbox.Group

checkbox.Group通过options传递数据。如何精准的控制单个checkbox的状态?

如下图:勾选checkbox的时候同步显示标签。对应的checkbox同步状态更改 一开始想到改造原始数据,给每个数据添加一个isChecked状态来控制其选中状态,但是发现在checkbox的change事件里通过e.target.check值和当前值无法同步。 后来改变了思路。还是利用checkbox.Group能自动获取到选中的checkbox数组的特征,通过把checkbox变成受控组件来实现。基本实现逻辑如下

jsx
import React, { useState, forwardRef, useImperativeHandle, useRef, useEffect } from 'react'
import { Modal, Checkbox, Tag, List } from "antd"
import "./quotaModalStyles/createCatagoryAttrs.less"

const data = [
	{ value: '001', label: "把" },
	{ value: '002', label: "个" },
	{ value: '003', label: "块" },
	{ value: '004', label: "打" },
	{ value: '005', label: "批" },
]


function Dialog(props, ref) {

	const { confirmFn } = props
	const [visible, setVisible] = useState(false)
	const [list, setList] = useState(data)
	const [checkedList, setCheckedList] = useState([])
	const [arr, setArr] = useState([])


	//lifecycle
	useEffect(() => {
		let temp = data.map(item => ({ ...item, isChecked: false }))
		setList(temp)
	}, [])

	//methods--
	// modal的显示与隐藏
	const showModal = () => {
		setVisible(true)
	}
	const hideModal = () => {
		setVisible(false)
	}
	useImperativeHandle(ref, () => ({
		showModelRef: showModal,
		hideModelRef: hideModal
	}))

	const handleClose = () => {
		hideModal();
		let num = 100;
		console.log("close")
		confirmFn(num)

	}
	const selectedItem = (values) => {
		console.log("values:", values)
		console.log("arr:", arr)

		setArr(values)
		console.log("b--:", b)
		let b = [];
		let temp = values
		list.forEach(item => {
			for (let j = 0; j < temp.length; j++) {
				if (item.value === temp[j]) {
					b.push(item)
				}
			}
		})
		setCheckedList(b)
	}
	const closeTag = (tag) => {
		console.log("closeTag-tag-:", tag)
		let tags = checkedList.filter(item => item.value !== tag.value)
		let res = [];
		tags.forEach(item => res.push(item.value))
		setArr(res)
		setCheckedList(tags)
	}

	return (
		<Modal
			icon=""
			title="设置属性值"
			width={670}
			footer={null}
			visible={visible}
			centered={true}
			maskClosable={false}
			onCancel={handleClose}
			destroyOnClose={true}
		>
			<div className='create-catagory-attrs-modal'>
				{<Checkbox.Group
					onChange={selectedItem}
					value={arr}
					options={list}
				>
				</Checkbox.Group>
				}
				<div className="tag-box">
					{
						checkedList.map(item => (
							<Tag closable onClose={() => closeTag(item)}>
								{
									item.label
								}
							</Tag>
						))
					}
				</div>
			</div>
		</Modal>
	)
}

export default forwardRef(Dialog)

注意这里我们给checkbox.Group的value赋值给一个数组,所谓受控组件:就是checkbox本身的状态受控于数据arr而不是再根据用户本身是否勾选来控制了。 而checkbox.Group的value被绑定到了arr。 arr表示被选中的数组列表,这样就意味着哪些数据被勾选了,当前就应该哪些被勾选

Form.Item相关

form.item 带上name的表示为受控组件,不能通过onChange直接赋值

Form.item支持嵌套,但是一个Form.item只能包裹一个输入

如果你需要 antd 样式的label,可以通过外部包裹 Form.Item 来实现

Form.item如果包裹CheckBox\Switch,需要添加属性 valuePropName="checked"

<Form.Item>
    <Form.Item name="remember" valuePropName="checked" noStyle>
      <Checkbox>Remember me</Checkbox>
    </Form.Item>

    <a className="login-form-forgot" href="">
      Forgot password
    </a>
</Form.Item>

Form.Item noStyle

多用于嵌套Form.Item的场景中

<Form.Item label="导出路径" {...filePathItemRadioLayout}>
    <Input.Group compact>
            <Form.Item
                    name={'filePath'}
                    noStyle
                    rules={[{required: true, message: '请选择导出文件路径'}]}
            >
                    <Input style={{width: 'calc(100% - 66px)'}} />
            </Form.Item>
            <Button onClick={handleSelectPath}>选择</Button>
    </Input.Group>
</Form.Item>

<Form.Item label="InputNumber">
    <Form.Item name="input-number" noStyle>
      <InputNumber min={1} max={10} />
    </Form.Item>
    <span className="ant-form-text"> machines</span>
</Form.Item>

form.getFieldValue

可以在form.item内部使用getFieldValue,获取对应字段名的值

<Form.Item
      label="User List"
      shouldUpdate={(prevValues, curValues) => prevValues.users !== curValues.users}
    >
  {({ getFieldValue }) => {
    const users: UserType[] = getFieldValue('users') || [];
    return users.length ? (
      <ul>
        {users.map((user, index) => (
          <li key={index} className="user">
            <Avatar icon={<UserOutlined />} />
            {user.name} - {user.age}
          </li>
        ))}
      </ul>
    ) : (
      <Typography.Text className="ant-form-text" type="secondary">
        ( <SmileOutlined /> No user yet. )
      </Typography.Text>
    );
  }}
</Form.Item>

确认密码:首先保证必填; 其次若值与密码一致,则返回成功

<Form.Item
   name="confirm"
   label="Confirm Password"
   dependencies={['password']}
   hasFeedback
   rules={[
     {
       required: true,
       message: 'Please confirm your password!',
     },
     ({ getFieldValue }) => ({
       validator(_, value) {
         if (!value || getFieldValue('password') === value) {
           return Promise.resolve();
         }
         return Promise.reject(new Error('The two passwords that you entered do not match!'));
       },
     }),
   ]}
 >
   <Input.Password />
</Form.Item>

Form.item 设置 shouldUpdate 自定义字段更新逻辑

Form 通过增量更新方式,只更新被修改的字段相关组件以达到性能优化目的。 大部分场景下,你只需要编写代码或者与dependencies属性配合校验即可。 而在某些特定场景,例如修改某个字段值后出现新的字段选项、或者纯粹希望表单任意变化都对某一个区域进行渲染。你可以通过shouldUpdate 修改 Form.Item 的更新逻辑。

shouldUpdatetrue时,Form 的任意变化都会使该 Form.Item 重新渲染。 这对于自定义渲染一些区域十分有帮助:

<Form.Item shouldUpdate>
  {() => {
    return <pre>{JSON.stringify(form.getFieldsValue(), null, 2)}</pre>;
  }}
</Form.Item>

shouldUpdate为方法时,表单的每次数值更新都会调用该方法,提供原先的值与当前的值以供你比较是否需要更新。这对于是否根据值来渲染额外字段十分有帮助:

<Form.Item shouldUpdate={(prevValues, curValues) => prevValues.additional !== curValues.additional}>
  {() => {
    return (
      <Form.Item name="other">
        <Input />
      </Form.Item>
    );
  }}
</Form.Item>

Form.Item Row Col

image.png

<Form.Item label="Captcha" extra="We must make sure that your are a human.">
    <Row gutter={8}>
      <Col span={12}>
        <Form.Item
          name="captcha"
          noStyle
          rules={[{ required: true, message: 'Please input the captcha you got!' }]}
        >
          <Input />
        </Form.Item>
      </Col>
      <Col span={12}>
        <Button>Get captcha</Button>
      </Col>
    </Row>
</Form.Item>

labelCol的使用:是一个对象,可以根据span和offset的比例可以来调整标签布局

label 标签布局,同 <Col> 组件,设置 span offset 值,如 {span: 3, offset: 12} 或 sm:

Form.Item 设置tooltip

image.png

<Form.Item label="Field A" required tooltip="This is a required field">
        <Input placeholder="input placeholder" />
</Form.Item>
<Form.Item
    label="Field B"
    tooltip={{ title: 'Tooltip with customize icon', icon: <InfoCircleOutlined /> }}
  >
    <Input placeholder="input placeholder" />
</Form.Item>

Form.Item是 文件上传的话, valuePropName="fileList"

getValueFromEvent 设置如何将 event 的值转换成字段值

ts
const normFile = (e: any) => {
	console.log('Upload event:', e);
	if (Array.isArray(e)) {
		return e;
	}
	return e?.fileList;
};
<Form.Item
    name="upload"
    label="Upload"
    valuePropName="fileList"
    getValueFromEvent={normFile}
    extra="longgggggggggggggggggggggggggggggggggg"
  >
    <Upload name="logo" action="/upload.do" listType="picture">
      <Button icon={<UploadOutlined />}>Click to upload</Button>
    </Upload>
</Form.Item>

自动提示 AutoComplete

ts
const [autoCompleteResult, setAutoCompleteResult] = useState<string[]>([]);

const onWebsiteChange = (value: string) => {
	if (!value) {
		setAutoCompleteResult([]);
	} else {
		setAutoCompleteResult(['.com', '.org', '.net'].map(domain => `${value}${domain}`));
	}
};

const websiteOptions = autoCompleteResult.map(website => ({
	label: website,
	value: website,
}));
tsx
<Form.Item
	name="website"
	label="Website"
	rules={[{ required: true, message: 'Please input website!' }]}
>
	<AutoComplete options={websiteOptions} onChange={onWebsiteChange} placeholder="website">
		<Input />
	</AutoComplete>
</Form.Item>

表单校验

Form.Item 设置的rules是一个数组

tsx
 <Form.Item
	name="email"
	label="E-mail"
	rules={[
		{
			type: 'email',
			message: 'The input is not valid E-mail!',
		},
		{
			required: true,
			message: 'Please input your E-mail!',
		},
	]}
>
	<Input />
</Form.Item>

whitespace: true 如果字段仅包含空格则校验不通过,只在type: 'string'时生效

tsx
 <Form.Item
	name="nickname"
	label="Nickname"
	tooltip="What do you want others to call you?"
	rules={[{ required: true, message: 'Please input your nickname!', whitespace: true }]}
>
	<Input />
</Form.Item>

自定义 validator

你可以选择通过async返回一个 promise 或者使用try...catch进行错误捕获

validator: async (rule, value) => {
  throw new Error('Something wrong!');
}

// or

validator(rule, value, callback) => {
  try {
    throw new Error('Something wrong!');
  } catch (err) {
    callback(err);
  }
}

Form.Item dependencies 设置依赖字段

当字段间存在依赖关系时使用。如果一个字段设置了dependencies属性。 那么它所依赖的字段更新时,该字段将自动触发更新与校验。 一种常见的场景,就是注册用户表单的“密码”与“确认密码”字段。 “确认密码”校验依赖于“密码”字段,设置dependencies后,“密码”字段更新会重新触发“校验密码”的校验逻辑

<Form.Item
        name="confirm"
        label="Confirm Password"
        dependencies={['password']}
        hasFeedback
        rules={[
          {
            required: true,
            message: 'Please confirm your password!',
          },
          ({ getFieldValue }) => ({
            validator(_, value) {
              if (!value || getFieldValue('password') === value) {
                return Promise.resolve();
              }
              return Promise.reject(new Error('The two passwords that you entered do not match!'));
            },
          }),
        ]}
      >
        <Input.Password />
</Form.Item>

dependencies不应和shouldUpdate一起使用,因为这可能带来更新逻辑的混乱。

hasFeedback 配合 validateStatus 属性使用,展示校验状态图标,建议只配合 Input 组件使用

image.png

校验项是数组

tsx
<Form.Item
	name="residence"
	label="Habitual Residence"
	rules={[
		{ type: 'array', required: true, message: 'Please select your habitual residence!' },
	]}
>
	<Cascader options={residences} />
</Form.Item>

手动触发异步校验

tsx
const onCheck = async () => {
	try {
		const values = await form.validateFields();
		console.log('Success:', values);
	} catch (errorInfo) {
		console.log('Failed:', errorInfo);
	}
};
tsx
form.validateFields()
	.then(values => {
		form.resetFields();
		onCreate(values);
	})
	.catch(info => {
		console.log('Validate Failed:', info);
	});

form.validateFields 返回示例

ts
validateFields()
	.then(values => {
		/*
	values:
		{
			username: 'username',
			password: 'password',
		}
	*/
	})
	.catch(errorInfo => {
		/*
		errorInfo:
			{
				values: {
					username: 'username',
					password: 'password',
				},
				errorFields: [
					{ name: ['password'], errors: ['Please input your Password!'] },
				],
				outOfDate: false,
			}
		*/
	});

scrollToFirstError || scrollToField->提交失败时自动滚动到指定字段

① scrollToFirstError

提交失败自动滚动到第一个错误字段。

注意:必须使用表单submit提交才会触发。

② scrollToField

如果不使用表单submit提交,使用scrollToField滚动到对应字段位置:

tsx
const save = async () => {
	try {
		const values = await form.validateFields();
	} catch (errorInfo) {
		// 滚动到第一个错误字段
		form.scrollToField(errorInfo.errorFields[0].name.toString(), { block: 'center' })
		// form.scrollToField方法可以有第二个入参,传入一个对象,其中block字段可以控制滚动到的位置
		// 可以查看antd文档了解第二个入参的更多信息
	}
}

Form相关

Form layout="inline" 会将每一项输入框在一行进行排列

const [form] = Form.useForm();

需要将form关联到Form标签上

form.setFieldsValue

支持对象深层次修改单个属性,例如

tsx
const toggleAllSelected = (checked: boolean) => {
	form.setFieldsValue({
		originChannel: { checked },
		extractChromatogram: { checked },
		extractMassSpectrum: { checked },
	})
}

如果在Form表单中onChange事件中,手写了一个setFieldsValue, 则不会生效。 原因是因为: Form表单会在手写的onChange事件之后执行内部的setFieldsValue,所以会将我们之前手写的setFieldsValue给覆盖

form.resetFields();

重置一组字段到initialValues

设置确认按钮在表单所有字段未都被触摸过或表单校验有误时禁用

shouldUpdate 表单值改变就要刷新 form.isFieldsTouched(true) 检查一组字段是否被用户操作过,allTouchedtrue时检查是否所有字段都被操作过 !form.isFieldsTouched(true) 检查是否所有字段没有都被操作过

form.getFieldsError() 获取一组字段名对应的错误信息,返回为数组形式

tsx
 <Form.Item shouldUpdate>
	{() => (
		<Button
			type="primary"
			htmlType="submit"
			disabled={
				!form.isFieldsTouched(true) ||
				!!form.getFieldsError().filter(({ errors }) => errors.length).length
			}
		>
			Log in
		</Button>
	)}
</Form.Item>

{...formItemLayout}

const formItemLayout = {
  labelCol: {
    xs: { span: 24 },
    sm: { span: 8 },
  },
  wrapperCol: {
    xs: { span: 24 },
    sm: { span: 16 },
  },
};
const tailFormItemLayout = {
  wrapperCol: {
    xs: {
      span: 24,
      offset: 0,
    },
    sm: {
      span: 16,
      offset: 8,
    },
  },
};

Form.useFormInstance

4.20.0新增,获取当前上下文正在使用的 Form 实例,常见于封装子组件消费无需透传 Form 实例

const Sub = () => {
  const form = Form.useFormInstance();

  return <Button onClick={() => form.setFieldsValue({})} />;
};

export default () => {
  const [form] = Form.useForm();

  return (
    <Form form={form}>
      <Sub />
    </Form>
  );
};

Form.useWatch

4.20.0新增,用于直接获取 form 中字段对应的值。通过该 Hooks 可以与诸如useSWR进行联动从而降低维护成本:

tsx
const Demo = () => {
	const [form] = Form.useForm();
	const userName = Form.useWatch('username', form);

	const { data: options } = useSWR(`/api/user/${userName}`, fetcher);

	return (
		<Form form={form}>
			<Form.Item name="username">
				<AutoComplete options={options} />
			</Form.Item>
		</Form>
	);
};

Form 仅会对变更的 Field 进行刷新,从而避免完整的组件刷新可能引发的性能问题。 因而你无法在 render 阶段通过form.getFieldsValue来实时获取字段值,而useWatch提供了一种特定字段访问的方式,从而使得在当前组件中可以直接消费字段的值。 同时,如果为了更好的渲染性能,你可以通过 Field 的 renderProps 仅更新需要更新的部分。 而当当前组件更新或者 effect 都不需要消费字段值时,则可以通过onValuesChange将数据抛出,从而避免组件更新。

FAQ

为什么 Form.Item 下的子组件defaultValue不生效?#

当你为 Form.Item 设置name属性后,子组件会转为受控模式。 因而defaultValue不会生效。你需要在 Form 上通过initialValues设置默认值。

Form 的 initialValues 与 Item 的 initialValue 区别?#

在大部分场景下,我们总是推荐优先使用 Form 的initialValues。 只有存在动态字段时你才应该使用 Item 的initialValue。 默认值遵循以下规则:

  1. Form 的initialValues拥有最高优先级
  2. Field 的initialValue次之*. 多个同nameItem 都设置initialValue时,则 Item 的initialValue不生效

为什么字段设置rules后更改值onFieldsChange会触发三次?#

字段除了本身的值变化外,校验也是其状态之一。 因而在触发字段变化会经历以下几个阶段:

  1. Trigger value change
  2. Rule validating
  3. Rule validated

在触发过程中,调用isFieldValidating会经历false>true>false的变化过程。

Contributors

作者:Long Mo
字数统计:8.6k 字
阅读时长:35 分钟
Long Mo
文章作者:Long Mo
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Longmo Docs