React
从2022年开始就想学习react,23年才真正有机会接触并系统的学习React。下面是我在工作中遇到的问题,记录下来方便自己或者同样是小白的你避坑。
Ant Design 3.0
因为工作的问题,并没有直接接触到antd最新的版本。在antd3的form表单提交中遇到过很多问题,下面列出一些错误的点或者代码,并解释为什么出现这种错误,以及我是怎么做解决掉这个问题的。
Form提交问题
父组件表单提交没有触发子组件表单的校验规则
问题描述:刚开始我是写了三个组件A,B,C。A中定义Form标签,A中包含B组件,B中包含C。B和C中只写入Form.Item标签。当我点击A中的提交按钮时我发现了一个问题,提交时并没有触发B、C中Form.Item里面的校验规则。
原因及解决:我在A、B、C三个组件中都使用了@Form.create()注解。下面解释一下@Form.create()是干啥的
@Form.create()是Ant Design中的一个装饰器(Decorator),用于将一个普通组件转换为带有表单功能的组件。该装饰器函数接受一个配置对象作为参数,用于配置表单的属性和行为。常用的配置项包括:
mapPropsToFields(props):将props对象中的属性映射为表单字段的初始值。
onFieldsChange(props, fields):用于监听表单字段的变化,当表单字段发生变化时会自动调用该函数。
onValuesChange(props, changedValues, allValues):当表单值发生变化时,自动调用该函数。
handleSubmit(e):表单提交的处理函数。
handleReset(e):表单重置的处理函数。
使用示例:
import { Form } from 'antd';
@Form.create()
class MyForm extends React.Component {
// 指定表单提交的处理函数
handleSubmit = e => {
e.preventDefault();
this.props.form.validateFields((err, values) => {
if (!err) {
console.log('Received values of form: ', values);
}
});
};
render() {
const { getFieldDecorator } = this.props.form;
return (
<Form onSubmit={this.handleSubmit}>
<Form.Item label="Username">
{getFieldDecorator('username', {
rules: [{ required: true, message: 'Please input your username!' }],
})(<Input />)}
</Form.Item>
<Form.Item label="Password">
{getFieldDecorator('password', {
rules: [{ required: true, message: 'Please input your password!' }],
})(<Input type="password" />)}
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
Log in
</Button>
<Button onClick={this.props.form.resetFields}>
Reset
</Button>
</Form.Item>
</Form>
);
}
}
export default MyForm;
简单理解一下,我在ABC三个组件中都加入这个组件之后,他们相当于三个不同的form表单,提交A并不会触发B和C中表单域的校验。
去掉B和C中的注解就解决了。
动态增删复制表单项
问题描述:a{b:[{c:[]}]}表单里的b和c都是可以动态改变的。我一开始的做法是参考百度出来的结果,用table标签包裹内容,增加、删除、复制的时候相当于对table的行进行操作,这么做确实解决了动态增删复制内容的需求,但是其中隐藏着一些问题。
首先大家都知道,table的数据来源是dataSource,例子如下:
const dataSource = [
{
key: '1',
name: '胡彦斌',
age: 32,
address: '西湖区湖底公园1号',
},
{
key: '2',
name: '胡彦祖',
age: 42,
address: '西湖区湖底公园1号',
},
];
const columns = [
{
title: '姓名',
dataIndex: 'name',
key: 'name',
},
{
title: '年龄',
dataIndex: 'age',
key: 'age',
},
{
title: '住址',
dataIndex: 'address',
key: 'address',
render: (test, record, index) => {
return (
<Form.Item>
{this.props.form.getFieldDecorator(`person[${index}].address`, {
initialValue: record.address,
})(
<Input/>
)}
</Form.Item>
)
}
},
];
<Table dataSource={dataSource} columns={columns} />;
用table包裹就是在columns里面写Form.Item,写法如上。
产生的第一个问题就是样式会有点丑,table有自己的边框,想去掉的话就得手动覆盖掉antd中的table样式,还是很麻烦的。
第二个问题就是,页面初始化之后dataSource的length是固定的,当length为1的时候表单域中只有person[0].address
,你点击新增行去动态增加Form.Item的时候,需要去更新表单域的值,新增一个person[1].address
,但是此时页面上还没有绑定此名称的控件,这个时候就会报错,报错信息我忘记了,大致意思就是你不能在在控件绑定之前操作该值。
后来我的解决办法就是参考antd3.0中动态增减表单项的例子,在表单域中新增keys,放dataSource的值,然后在页面渲染的时候便利keys中的值,动态渲染出表单项person。
这个过程中我也有遇到过问题,当页面初始化完了之后,假设dataSource的length的值为3,也就是person[0].address,person[1].address,person[2].address
有值,当我修改了第二项,也就是person[1]
时,删除第二项后,我们期望看到的是第二项被完整地删掉,可是实际情况是第二项没有被删掉,反而视觉上是第三行被删掉了。
原因是我在删除操作中只更新了表单域中keys的值,没有更新表单域中person的值,虽然视觉上少了一行,但是在表单域中还是有三行数据。解决办法就是在删除操作中同时更新keys和person的值。新增和复制方法就不用手动更新person的值,只需要更新keys的值,页面渲染的时候表单域中的person会自动增加一行。
第三个问题就是在给表单域绑定的时候,出现如下报错
One field name cannot be part of another, e.g. `a` and `a.b`
原因是我在绑定的时候出现了如下代码
<Form.Item>
{this.props.form.getFieldDecorator(`person`, {
initialValue: person,
})(
<Input/>
)}
</Form.Item>
<Form.Item>
{this.props.form.getFieldDecorator(`person[${index}].name`, {
initialValue: person.name,
})(
<Input/>
)}
</Form.Item>
不能同时绑定到a,又绑定a.b,这也是为什么上文中我绑定keys而不是直接绑定person的原因。
我还遇到过如下报错:
Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> from render. Or maybe you meant to call this function rather than return it.
原因是我在写this.props.form.getFieldDecorator的时候,后面没有写全
// 错误代码
{this.props.form.getFieldDecorator('id', {
initialValue: value.id})}
// 正确代码
{this.props.form.getFieldDecorator('id', {
initialValue: value.id
})(
<Input hidden={true}/>
)}
必须把表单域的值绑定到一个表单控件上。下面是getFieldDecorator的用法示例:
import { Form, Input } from 'antd';
const MyForm = ({ form }) => {
const { getFieldDecorator } = form;
return (
<Form>
<Form.Item label="Username">
{getFieldDecorator('username', {
rules: [{ required: true, message: 'Please input your username!' }],
initialValue: 'John', // 设置初始值
valuePropName: 'defaultValue', // 指定绑定的属性
})(<Input />)}
</Form.Item>
</Form>
);
};
const WrappedMyForm = Form.create()(MyForm);
export default WrappedMyForm;
在上述代码中,form.getFieldDecorator(‘username’, {…})方法将表单项与表单数据关联起来,通过指定initialValue属性来设置初始值,并通过valuePropName属性将表单值绑定到组件的defaultValue属性上。这样,在渲染时,输入框的初始值将被设置为"John"。
根据已选择的表单项动态的改变另一表单项
通过三目运算符来选择
<Form>
<Form.Item label="多选项">
{getFieldDecorator('type', {
initialValue: item.type
})(
<Select>
<Option value="1">类型1</Option>
<Option value="2">类型2</Option>
</Select>
)}
</Form.Item>
<Form.Item label="可变">
{getFieldValue('type') === 1
? getFieldDecorator('name', {
initialValue: item.name,
rules: [{required: true}],
})(<Input/>)
: getFieldDecorator('name', {
initialValue: item.name,
rules: [{required: false}],
})(<Input/>)
}
</Form.Item>
</Form>
注意,这里用的是
getFieldValue('type') === 1
而不是item.type === 1