table 表格
自定义选择项
筛选和排序
固定表头、列、可展开、可展开、表格行/列合并
*服务端筛选、排序等功能以及页面 loading 效果
// column 表格列的配置描述 - [] width 列宽度 - string | number fixed 列是否固定 - 可选 true (等效于 left) left right // pagination 分页器 - objects position 指定分页显示的位置 - topLeft | topCenter | topRight |bottomLeft | bottomCenter | bottomRight pageSize: 50 // scroll 表格是否可滚动,也可以指定滚动区域的宽、高 - object x - 设置横向滚动,也可用于指定滚动区域的宽,可以设置为像素值,百分比,true 和 'max-content' y - 设置纵向滚动
引入
import { Table, Tag, Space } from 'antd'; const { Column, ColumnGroup } = Table; // jsx 风格
实例
const columns = [ { fixed: 'left', // (IE 下无效)列是否固定,可选 true (等效于 left) left right width: 300, // '30%' 列宽度 title: 'Name', // 列头显示文字 dataIndex: 'name', // 列数据在数据项中对应的路径,支持通过数组查询嵌套路径 key: 'name', // React 需要的 key,如果已经设置了唯一的 dataIndex,可以忽略这个属性 // 表头列合并,设置为 0 时,不渲染 colSpan: 0, render: text => <a>{text}</a>, // 生成复杂数据的渲染函数,参数分别为当前行的值,当前行数据,行索引 function(text, record, index) {} // 使用 render 里的单元格属性 colSpan 或者 rowSpan 设值为 0 时,设置的表格不会渲染 render: (value, row, index) => { const obj = { children: value, props: {}, }; if (index === 2) { obj.props.rowSpan = 2; } // These two are merged into above cell if (index === 3) { obj.props.rowSpan = 0; } if (index === 4) { obj.props.colSpan = 0; } return obj; }, // 对某一列数据进行筛选,使用列的 filters 属性来指定需要筛选菜单的列,onFilter 用于筛选当前数据,filterMultiple 用于指定多选和单选。 filters: [ { text: 'Joe', value: 'Joe', }, { text: 'Submenu', value: 'Submenu', children: [ { text: 'Green', value: 'Green', }, ], }, // 指定筛选结果的条件 // 这里是查找以`value'开头的名称` onFilter: (value, record) => record.name.indexOf(value) === 0, // 本地模式下,确定筛选的运行函数。筛选的值从filters中提取。record.name为字符串,筛选开头为什么的行 // 对某一列数据进行排序,通过指定列的 sorter 函数即可启动排序按钮。sorter: function(rowA, rowB) { ... }, rowA、rowB 为比较的两个行数据 sorter: (a, b) => a.name.length - b.name.length, // 排序函数,本地排序使用一个函数(参考 Array.sort 的 compareFunction),需要服务端排序可设为 true sorter: { // column.sorter 支持 multiple 字段以配置多列排序优先级。通过 sorter.compare 配置排序逻辑,你可以通过不设置该函数只启动多列排序的交互形式 compare: (a, b) => a.chinese - b.chinese, // 排序方式 multiple: 3, // 排序的优先级,如果有分数相同的话,分数相同依据 multiple 2 的排序在排序, multiple 1 就不起作用了 }, // 你可以通过设置 ['ascend', 'descend', 'ascend'] 禁止排序恢复到默认状态。 sortDirections: ['descend'], // 支持的排序方式,覆盖 Table 中 sortDirections, 取值为 ascend descend。表格会出现的排列方式 // 使用 defaultSortOrder 属性,设置列的默认排序顺序 defaultSortOrder: 'descend', // 默认排序顺序。descend为降序排列,ascend为升序排列 // 树型筛选菜单 filterMode: 'tree', // 指定筛选菜单的用户界面。来修改筛选菜单的 UI,可选值有 menu(默认)和 tree filterSearch: true, // 筛选菜单项是否可搜索。开启筛选项的搜索 // 可控的筛选和排序。columns 中定义了 filteredValue 和 sortOrder 属性即视为受控模式。 // 只支持同时对一列进行排序,请保证只有一列的 sortOrder 属性是生效的。 // 务必指定 column.key sortOrder: sortedInfo.columnKey === 'name' && sortedInfo.order, // 排序的受控属性,外界可用此控制列的排序,可设置为 ascend descend false filteredValue: filteredInfo.name || null, // 筛选的受控属性,外界可用此控制列的筛选状态,值为已筛选的 value 数组 // 超过宽度将自动省略,暂不支持和排序筛选一起使用。设置为 true 或 { showTitle?: boolean } 时,表格布局将变成 tableLayout="fixed"。 ellipsis: true, ellipsis: { showTitle: false, }, render: address => ( <Tooltip placement="topLeft" title={address}> {address} </Tooltip> ), } ] const data = [ { key: '1', name: 'chaoyue', age: 32, address: 'New York No. 1 Lake Park', tags: ['nice', 'developer'], // 树形数据展示 children: [ { key: 11, name: 'John Brown', age: 42, address: 'New York No. 2 Lake Park', }, { key: 12, name: 'John Brown jr.', age: 30, address: 'New York No. 3 Lake Park', children: [ { key: 121, name: 'Jimmy Brown', age: 16, address: 'New York No. 3 Lake Park', }, ], }, ] // 分页、排序、筛选变化时触发。因此下面两个赋值才会使sortOrder、filteredValue排序或过滤 const onChange = (pagination, filters, sorter, extra) => { console.log('params', pagination, filters, sorter, extra); setFilteredInfo(filters) setSortedInfo(sorter) // 发送网络请求 this.fetch({ sortField: sorter.field, sortOrder: sorter.order, pagination, ...filters, }); } fetch = (params = {}) => { this.setState({ loading: true }); reqwest({ url: 'https://randomuser.me/api', method: 'get', type: 'json', data: getRandomuserParams(params), }).then(data => { console.log(data); this.setState({ loading: false, data: data.results, pagination: { ...params.pagination, total: 200, // 200 is mock data, you should read it from server // total: data.totalCount, }, }); }); }; // 其他组件调用方法处理 Table组件 clearFilters = () => { setFilteredInfo(null) }; clearAll = () => { setFilteredInfo(null) setSortedInfo(null) }; setAgeSort = () => { setSortedInfo({ order: 'descend', // age列 降序排列 columnKey: 'age', // 排序的 age列 }) };
Table 组件
<Table // 正常配置 bordered // 是否展示外边框和列边框 title={() => 'Header'} // 表格标题。 function(currentPageData) footer={() => 'Footer'} // 表格尾部。function(currentPageData) size="small" // 表格大小 // 配置展开属性 expandable={{ // 额外的展开行。function(record, index, indent, expanded): ReactNode expandedRowRender: record => <p style={{ margin: 0 }}>{record.description}</p>, // 设置是否允许行展开。 (record) => boolean rowExpandable: record => record.name !== 'Not Expandable', }} // 数据展示配置 columns={columns} // 表格列的配置描述 [] dataSource={data} // 数据数组 // 表格行是否可选择 {} rowSelection={ { type:checkbox | radio, // 多选/单选 onChange: (selectedRowKeys, selectedRows) => { // 选中项发生变化时的回调,selectedRowKeys为key的值,selectedRows为选中行的[]数据 console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows); }, getCheckboxProps: (record) => ({ // 选择框的默认属性配置 disabled: record.name === 'chaoyue', // 如果data的数据中的name等于`chaoyue`,默认 禁止点击 name: record.name, }), selectedRowKeys: [], // 指定选中项的 key 数组[],需要和 onChange 进行配合 selections:{ // 自定义选择项 配置项, 设为 true 时使用默认选择项。s配置项 key-react的key值,text-选中的文字,onSelect-选择项点击回调-function(changeableRowKeys) Table.SELECTION_ALL, // 选择全部 Table.SELECTION_INVERT, // 反向选择 Table.SELECTION_NONE, // 清除所有 { // 自定义的 偶数选择 key: 'odd', text: '选择偶数', onSelect: changableRowKeys => { let newSelectedRowKeys = []; newSelectedRowKeys = changableRowKeys.filter((key, index) => { if (index % 2 !== 0) { return false; } return true; }); this.setState({ selectedRowKeys: newSelectedRowKeys }); }, }, // 分页、排序、筛选变化时触发。function(pagination, filters, sorter, extra: { currentDataSource: [], action: paginate | sort | filter }) onChange={onChange} // 固定头和列 // 表格是否可滚动,也可以指定滚动区域的宽、高 scroll={{ x: 1500, // 设置横向滚动,也可用于指定滚动区域的宽,可以设置为像素值,百分比,true 和 'max-content' y: 300, // 设置纵向滚动,也可用于指定滚动区域的高,可以设置为像素值 }} // 分页器,参考配置项或 pagination 文档,设为 false 时不展示和进行分页 pagination={{ pageSize: 50 }} // 表格行 key 的取值,可以是字符串或一个函数。record为函数参数 rowKey={record => record.login.uuid} // 页面是否加载中 loading={loading} // 覆盖默认的 table 元素 components={components} // 表格行的类名。function(record, index) rowClassName={() => 'editable-row'} // 总结栏。(currentData) => ReactNode summary={() => ( <Table.Summary fixed> <Table.Summary.Row> <Table.Summary.Cell index={0}>Summary</Table.Summary.Cell> <Table.Summary.Cell index={1}>This is a summary content</Table.Summary.Cell> </Table.Summary.Row> </Table.Summary> )} } } />
*自定义筛选菜单
state = { searchText: '', searchedColumn: '', }; // 在 colmnn 下的 getColumnSearchProps = dataIndex => ({ // 假定为 name // 可以自定义筛选菜单,此函数只负责渲染图层,需要自行编写各种交互 filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => ( // 函数结构传递四个参数,还有其他api // 自定义 筛选 <div style={{ padding: 8 }}> <Input ref={node => { // 获取dom结构 this.searchInput = node; }} placeholder={`搜索${dataIndex}`} // 搜索name value={selectedKeys[0]} // 搜索框显示的值,依赖selectedKeys。依次推断 selectedKeys为数组[] onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])} // 搜索框发生变化时保存selectedKeys的值 onPressEnter={() => this.handleSearch(selectedKeys, confirm, dataIndex)} // 等同下方的搜索按钮的功能 style={{ marginBottom: 8, display: 'block' }} /> <Space> <Button type="primary" onClick={() => this.handleSearch(selectedKeys, confirm, dataIndex)} // 等同上方的输入框的按下回车的回调 icon={<SearchOutlined />} size="small" style={{ width: 90 }} > 搜索 </Button> <Button onClick={() => this.handleReset(clearFilters)} // 点击 触发过滤重置,搜索框内容为空 size="small" style={{ width: 90 }}> 重置 </Button> <Button type="link" size="small" onClick={() => { confirm({ closeDropdown: false }); // 是否关闭筛选菜单 this.setState({ searchText: selectedKeys[0], // 设置 searchText 的值 searchedColumn: dataIndex, // 设置 searchedColumn的值为 name }); }} > 过滤 </Button> </Space> </div> ), // 自定义 filter 图标 filterIcon: filtered => <SearchOutlined style={{ color: filtered ? '#1890ff' : undefined }} />, // 确定筛选的运行函数 onFilter: (value, record) => record[dataIndex] ? record[dataIndex].toString().toLowerCase().includes(value.toLowerCase()) : '', // 自定义筛选菜单可见变化时调用。带visible参数的回调函数 onFilterDropdownVisibleChange: visible => { if (visible) { setTimeout(() => this.searchInput.select(), 100); } }, render: text => this.state.searchedColumn === dataIndex ? ( <Highlighter // 被搜索的文字 高亮显示 highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }} searchWords={[this.state.searchText]} autoEscape textToHighlight={text ? text.toString() : ''} /> ) : ( text ), }); handleSearch = (selectedKeys, confirm, dataIndex) => { confirm(); // 关闭筛选菜单 this.setState({ searchText: selectedKeys[0], searchedColumn: dataIndex, }); }; handleReset = clearFilters => { clearFilters(); // 清楚搜索 this.setState({ searchText: '' }); }; const column = [ { title: 'Address', dataIndex: 'address', key: 'address', // 通过 filterDropdown 自定义的列筛选功能,并实现一个搜索列的示例。 // 给函数 confirm 添加 boolean 类型参数 closeDropdown,是否关闭筛选菜单,默认为 true。 ...this.getColumnSearchProps('address'), sorter: (a, b) => a.address.length - b.address.length, // 排序 sortDirections: ['descend', 'ascend'], // 支持的两种排序方式,在排序有按钮 }, ]
*可编辑单元格、可编辑行、嵌套子表格
Form 表单
const [form] = Form.useForm(); // 前提 <Form form={form} ...> <Form.Item name="note" ... > <Input /> </Form.Item> </Form> form.setFieldsValue({ note: 'chaoyue' }); // 可以设置input的值 form.resetFields(); // 重置所有的值为空 form.getFieldValue('chaoyue') // 获取input的值
实验
const onGenderChange = (value) => { switch (value) { case 'male': form.setFieldsValue({ note: 'Hi, man!' }); return; case 'female': form.setFieldsValue({ note: 'Hi, lady!' }); return; case 'other': form.setFieldsValue({ note: 'Hi there!' }); } }; const onFinishFailed = (values, errorFields, outOfDate) => { //提交失败 console.log('Failed:', values, errorFields, outOfDate); }; const onFinish = (values) => { // 提交成功 console.log(values); }; const onReset = () => { // 重置 form.resetFields(); // 重置一组字段到 initialValues }; const onFill = () => { // 补全 form.setFieldsValue({ // 设置表单的值(该值将直接传入 form store 中。如果你不希望传入对象被修改,请克隆后传入) note: 'Hello world!', gender: 'male', }); }; const [form] = Form.useForm(); <Forms // label 标签布局,同 <Col> 组件,设置 span offset 值,如 {span: 3, offset: 12} 或 sm: {span: 3, offset: 12} labelCol: { span: 8 }, // 需要为输入控件设置布局样式时,使用该属性,用法同 labelCol // labelCol: { xs: { span: 24 }, sm: { span: 4 }, wrapperCol: { span: 16 }, // 表单名称,会作为表单字段 id 前缀使用 name="basic" // 表单默认值,只有初始化以及重置时生效 // 根据 form.item 的 name 生效 initialValues={{ remember: true, layoutName: 'horizontal', }} //提交表单且数据验证成功后回调事件 onFinish={ (values) => { console.log('Success:', values); // 例如:{username: '123', password: '21312', remember: true} // 主要依据 form.item 的name属性 } // 提交表单且数据验证失败后回调事件 onFinishFailed={ (errorInfo) => { console.log('Failed:', errorInfo); // 例如:{values: {…}, errorFields: Array(1), outOfDate: false} } // 输入框自动完成功能 autoComplete="off" // 经 Form.useForm() 创建的 form 控制实例,不提供时会自动创建 form={form} // 必选样式,可以切换为必选或者可选展示样式。此为 Form 配置,Form.Item 无法单独配置 requiredMark={ 'optional' | false | true } // 设置字段组件的尺寸(仅限 antd 组件) size={ 'small' | 'middle' | 'large' } // 表单布局 layout={ 'horizontal' | 'vertical' | 'inline' } // 字段值更新时触发回调事件。function(changedValues, allValues) onValuesChange={ (changedValues, allValues) => { console.log('changedValues, allValues:', changedValues, allValues); // 以布局的为例: {layoutName: 'vertical'} 、 {layoutName: 'vertical'} }; // 验证提示模板 validateMessages={ required: '${label} is required!', types: { email: '${label} is not a valid email!', number: '${label} is not a valid number!', }, number: { range: '${label} must be between ${min} and ${max}', }, } } > <Form.Item // label 标签的文本 label="名字" // 字段名,支持数组,一般嵌套的时候使用数组形式 name="username" name={['address', 'street']} // 校验规则,设置字段的校验逻辑 rules={[ { // 是否为必选字段 required: true, // 错误信息,不设置时会通过模板自动生成 message: '请输入你的名字', // 如果字段仅包含空格则校验不通过,只在 type: 'string' 时生效 whitespace: true, }, { // 类型,常见有 string |number |boolean |url | email type: 'url', // 仅警告,不阻塞表单提交 warningOnly: true, }, { // 类型,常见有 string |number |boolean |url | email type: 'string', // 必须设置 type:string 类型为字符串最小长度;number 类型时为最小值;array 类型时为数组最小长度 min: 6, // 必须设置 type:string 类型为字符串最大长度;number 类型时为最大值;array 类型时为数组最大长度 max: 99, }, { // 自定义校验,接收 Promise 作为返回值。 (rule, value) => Promise validator: (_, value) => { if (value.number > 0) { return Promise.resolve(); } return Promise.reject(new Error('Price must be greater than zero!')); } }, ]} // 必填样式设置。如不设置,则会根据校验规则自动生成。搭配form的requiredMark属性值 required // 子节点的值的属性,如 Switch 的是 'checked'。该属性为 getValueProps 的封装,自定义 getValueProps 后会失效 valuePropName="checked" // label 标签布局,同 <Col> 组件,设置 span offset 值,如 {span: 3, offset: 12} 或 sm: {span: 3, offset: 12}。你可以通过 Form 的 labelCol 进行统一设置,,不会作用于嵌套 Item。当和 Form 同时设置时,以 Item 为准 // 需要为输入控件设置布局样式时,使用该属性,用法同 labelCol。你可以通过 Form 的 wrapperCol 进行统一设置,不会作用于嵌套 Item。当和 Form 同时设置时,以 Item 为准 labelCol wrapperCol={{ offset: 8, span: 16, }} // 为 true 时不带样式,作为纯字段控件使用 noStyle // 自定义字段更新逻辑,说明见下 // boolean | (prevValue, curValue) => boolean shouldUpdate={(prevValues, currentValues) => prevValues.gender !== currentValues.gender} // 统一设置字段触发验证的时机 validateTrigger={['onChange', 'onBlur']} // 配置提示信息 tooltip={{ title: '提示信息', icon: <InfoCircleOutlined />, }} // 配合 validateStatus 属性使用,展示校验状态图标,建议只配合 Input 组件使用。 hasFeedback // 设置依赖字段。NamePath[] dependencies={['password']} // 额外的提示信息,和 help 类似,当需要错误信息和提示文案同时出现时,可以使用这个。 ReactNode extra="We must make sure that your are a human." // 子节点的值的属性,如 Switch 的是 'checked'。该属性为 getValueProps 的封装,自定义 getValueProps 后会失效 valuePropName="checked" // 自定义校验 // Form 具有自动收集数据并校验的功能,但如果您不需要这个功能,或者默认的行为无法满足业务需求,可以选择自行处理数据 // 校验状态,如不设置,则会根据校验规则自动生成,可选:'success' 'warning' 'error' 'validating' validateStatus='success' // 提示信息,如不设置,则会根据校验规则自动生成 help='The prime between 8 and 12 is 11!' // 配合 validateStatus 属性使用,展示校验状态图标,建议只配合 Input 组件使用 hasFeedback > {({ getFieldValue }) => // 获取对应字段名的值 getFieldValue('gender') === 'other' ? ( <Form.Item name="customizeGender" label="Customize Gender" rules={[{ required: true }]}> <Input /> </Form.Item> ) : null </Form.Item> <Form.Item name="gender" label="Gender" rules={[{ required: true }]}> <Select placeholder="Select a option and change input text above" onChange={this.onGenderChange} allowClear > <Option value="male">male</Option> <Option value="female">female</Option> <Option value="other">other</Option> </Select> </Form.Item> <Form.Item wrapperCol={{ offset: 8, span: 16 }}> <Button type="primary" htmlType="submit">{/* 设置 button 原生的 type 值,可选值请参考 HTML 标准 */} 提交 </Button> <Button htmlType="button" onClick={onReset}> 重置 </Button> <Button type="link" htmlType="button" onClick={onFill}> 填充 </Button> </Form.Item> </Form>
Form.item的特殊属性详解
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
dependencies | 设置依赖字段 | NamePath[] | - |
name | 字段名,支持数组 | NamePath | - |
rules | 校验规则,设置字段的校验逻辑 | Rule[] | - |
shouldUpdate | 自定义字段更新逻辑 | boolean | (prevValue, curValue) => boolean | false |
name -- 受控
被设置了 name
属性的 Form.Item
包装的控件,表单控件会自动添加 value
(或 valuePropName
指定的其他属性) onChange
(或 trigger
指定的其他属性),数据同步将被 Form 接管,这会导致以下结果:
1.你不再需要也不应该用 onChange
来做数据收集同步(你可以使用 Form 的 onValuesChange
),但还是可以继续监听 onChange
事件。
2.你不能用控件的 value
或 defaultValue
等属性来设置表单域的值,默认值可以用 Form 里的 initialValues
来设置。注意 initialValues
不能被 setState
动态更新,你需要用 setFieldsValue
来更新。
3.你不应该用 setState
,可以使用 form.setFieldsValue
来动态改变表单值。
shouldUpdate -- 是否更新
Form 通过增量更新方式,只更新被修改的字段相关组件以达到性能优化目的。
大部分场景下,你只需要编写代码或者与 dependencies
属性配合校验即可。而在某些特定场景,例如修改某个字段值后出现新的字段选项、或者纯粹希望表单任意变化都对某一个区域进行渲染。你可以通过 shouldUpdate
修改 Form.Item 的更新逻辑。
当 shouldUpdate
为 true
时,Form 的任意变化都会使该 Form.Item 重新渲染。这对于自定义渲染一些区域十分有帮助:
<Form.Item shouldUpdate> {() => { return <pre>{JSON.stringify(form.getFieldsValue(), null, 2)}</pre>; }} </Form.Item>
当 shouldUpdate
为方法时,表单的每次数值更新都会调用该方法,提供原先的值与当前的值以供你比较是否需要更新。这对于是否根据值来渲染额外字段十分有帮助:
<Form.Item noStyle shouldUpdate={(prevValues, currentValues) => prevValues.gender !== currentValues.gender} > {({ getFieldValue }) => getFieldValue('gender') === 'other' ? ( <Form.Item name="customizeGender" label="Customize Gender" rules={[{ required: true }]}> <Input /> </Form.Item> ) : null } </Form.Item>
dependencies -- 依赖项
当字段间存在依赖关系时使用。如果一个字段设置了 dependencies
属性。那么它所依赖的字段更新时,该字段将自动触发更新与校验。一种常见的场景,就是注册用户表单的“密码”与“确认密码”字段。“确认密码”校验依赖于“密码”字段,设置 dependencies
后,“密码”字段更新会重新触发“校验密码”的校验逻辑。
dependencies
不应和 shouldUpdate
一起使用,因为这可能带来更新逻辑的混乱。
<Form.Item name="password" label="密码" rules={[ { required: true, message: '请输入密码!', }, ]} hasFeedback > <Input.Password /> </Form.Item> <Form.Item name="confirm" label="二次确认密码" // 第一个密码发生了变化,会自动触发依赖的验证 dependencies={['password']} hasFeedback rules={[ // 效验第一个规则 { required: true, message: '请再次输入密码!', }, // 效验第二个规则 ({ getFieldValue }) => ({ validator(_, value) { if (!value || getFieldValue('password') === value) { return Promise.resolve(); } return Promise.reject(new Error('两次密码不一样!')); }, }), ]} > <Input.Password /> </Form.Item>
rules -- [{}, {}]
rules={[ // 效验第一个规则 { required: true, message: '请再次输入密码!', }, // 效验第二个规则 ({ getFieldValue }) => ({ validator(_, value) { if (!value || getFieldValue('password') === value) { return Promise.resolve(); } return Promise.reject(new Error('两次密码不一样!')); }, }), { required: true, message: '请输入原因', trigger: 'blur' }, { min: 2, message: '请输入不少于2个字符', trigger: 'blur' }, { pattern: /^[A-Za-z0-9\u4e00-\u9fa5]+$/, message: '不允许输入空格等特殊符号' } ]}
noStyle -- 无样式
这里演示 Form.Item
内有多个元素的使用方式。<Form.Item name="field" />
只会对它的直接子元素绑定表单功能,例如直接包裹了 Input/Select。如果控件前后还有一些文案或样式装点,或者一个表单项内有多个控件,你可以使用内嵌的 Form.Item
完成。你可以给 Form.Item
自定义 style
进行内联布局,或者添加 noStyle
作为纯粹的无样式绑定组件
- <Form.Item label="Field" name="field"> - <Input /> - </Form.Item> + <Form.Item label="Field"> + <Form.Item name="field" noStyle><Input /></Form.Item> // 直接包裹才会绑定表单 + <span>description</span> + </Form.Item>
这里展示了三种典型场景:
-
Username
:输入框后面有描述文案或其他组件,在Form.Item
内使用<Form.Item name="field" noStyle />
去绑定对应子控件。<Form.Item label="用户名"> <Space> <Form.Item name="username" noStyle rules={[{ required: true, message: 'Username is required' }]} > <Input style={{ width: 160 }} placeholder="Please input" /> </Form.Item> <Tooltip title="Useful information"> <Typography.Link href="#API">Need Help?</Typography.Link> </Tooltip> </Space> </Form.Item>
-
Address
:有两个控件,在Form.Item
内使用两个<Form.Item name="field" noStyle />
分别绑定对应控件。<Form.Item label="Address"> <Input.Group compact> <Form.Item name={['address', 'province']} noStyle rules={[{ required: true, message: 'Province is required' }]} > <Select placeholder="Select province"> <Option value="Zhejiang">Zhejiang</Option> <Option value="Jiangsu">Jiangsu</Option> </Select> </Form.Item> <Form.Item name={['address', 'street']} noStyle rules={[{ required: true, message: 'Street is required' }]} > <Input style={{ width: '50%' }} placeholder="Input street" /> </Form.Item> </Input.Group> </Form.Item>
-
BirthDate
:有两个内联控件,错误信息展示各自控件下,使用两个<Form.Item name="field" />
分别绑定对应控件,并修改style
使其内联布局。<Form.Item label="BirthDate" style={{ marginBottom: 0 }}> <Form.Item name="year" rules={[{ required: true }]} style={{ display: 'inline-block', width: 'calc(50% - 8px)' }} > <Input placeholder="Input birth year" /> </Form.Item> <Form.Item name="month" rules={[{ required: true }]} style={{ display: 'inline-block', width: 'calc(50% - 8px)', margin: '0 8px' }} > <Input placeholder="Input birth month" /> </Form.Item> </Form.Item>
注意,在 label 对应的 Form.Item 上不要在指定
name
属性,这个 Item 只作为布局作用。
动态增加表单项
*Form.List
参数 | 说明 | 类型 | 默认值 | 版本 |
---|---|---|---|---|
children | 渲染函数 | (fields: Field[], operation: { add, remove, move }, meta: { errors }) => React.ReactNode | - | |
initialValue | 设置子元素默认值,如果与 Form 的 initialValues 冲突则以 Form 为准 | any[] | - | 4.9.0 |
name | 字段名,支持数组 | NamePath | - | |
rules | 校验规则,仅支持自定义规则。需要配合 ErrorList 一同使用 | { validator, message }[] | - | 4.7.0 |
<Form.List> {(fields, { add, remove, move }, { errors }) => fields.map(field => ( <Form.Item {...field}> <Input /> </Form.Item> )) } </Form.List>
注意:Form.List 下的字段不应该配置 initialValue
,你始终应该通过 Form.List 的 initialValue
或者 Form 的 initialValues
来配置。
children下的operation选项值
Form.List 渲染表单相关操作函数。
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
add | 新增表单项 | (defaultValue?: any, insertIndex?: number) => void | insertIndex |
move | 移动表单项 | (from: number, to: number) => void | - |
remove | 删除表单项 | (index: number | number[]) => void | number[] |
-
add
方法参数可用于设置初始值。
Form.ErrorList -- 配合配合 Form.List 的 rules 一同使用
错误展示组件,仅限配合 Form.List 的 rules 一同使用
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
errors | 错误列表 | ReactNode[] | - |
实例
1.动态增减表单项
const formItemLayout = { // label 标签布局,同 <Col> 组件,设置 span offset 值,如 {span: 3, offset: 12} 或 sm: {span: 3, offset: 12} labelCol: { xs: { span: 24 }, sm: { span: 4 }, }, // 需要为输入控件设置布局样式时,使用该属性,用法同 labelCol wrapperCol: { xs: { span: 24 }, sm: { span: 20 }, }, }; const formItemLayoutWithOutLabel = { wrapperCol: { xs: { span: 24, offset: 0 }, sm: { span: 20, offset: 4 }, }, }; const DynamicFieldSet = () => { const onFinish = values => { console.log('获取表单所有的值:', values); }; return ( <Form // 表单名称,会作为表单字段 id 前缀使用 name="form-name-1" {...formItemLayoutWithOutLabel} onFinish={onFinish} > <Form.List // 字段名,支持数组。提交返回的value: chaoyue: [xx,xx,..] name="CHAOYUE" // 校验规则,仅支持自定义规则。需要配合 ErrorList 一同使用。{ validator, message }[] rules={[ { validator: async (rule, value) => { // 自定义校验,接收 Promise 作为返回值 console.log('查看验证的两个值', rule, value) // rule: { message: '错误提示', required: true, field: 'CHAOYUE', fullField: 'CHAOYUE', validator: ƒ, …, ...(等等在rules写的其他属性)}。 其中 field、fullField指的是 name对应的值 // value: 以[]的形式显示form.item 的值,如果没有为undefined if (!value || value.length < 2) { return Promise.reject(new Error('至少有两个值')); } }, message: '错误提示', required: true // ... 比如还有 max: 12等,参考rules所有属性 }, ]} > // children 渲染函数 // (fields: Field[], operation: { add, remove, move }, meta: { errors }) => React.ReactNode {(fields, { add, remove, move }, { errors }) => ( <> {fields.map((field, index) => { console.log('查看渲染的值', fields) // fields 为对象数组 // [{name: 0, key: 0, isListField: true, fieldKey: 0}, { name: 1, key: 1, isListField: true, fieldKey: 1}] return ( <Form.Item // 给我一个思路是 当不设置labelCol时候,label的值会默认消失 {...(index === 0 ? formItemLayout : formItemLayoutWithOutLabel)} label={index === 0 ? '请输入' : ''} required={false} key={field.key} > <Form.Item {...field} // 设置字段校验的时机,默认是 onChange validateTrigger={['onChange', 'onBlur']} rules={[ { required: true, // 如果字段仅包含空格则校验不通过,只在 type: 'string' 时生效 whitespace: true, message: "请输入值哈哈哈", }, ]} noStyle > <Input placeholder="是我增加进来的表单" style={{ width: '60%' }} /> </Form.Item> {fields.length > 1 ? (<MinusCircleOutlined className="delete" onClick={() => remove(field.name)} />) : null} </Form.Item> ) })} <Form.Item> <Button onClick={() => add()} style={{ width: '60%' }} icon={<PlusOutlined />}>增加一个</Button> {/* add('表单内部显示内容', 0);增加一个表单在最前面,输入框的内容为 '表单内部显示内容' */} <Button onClick={() => { add('最前面的表单显示的内容', 0); }} style={{ width: '60%', marginTop: '20px' }} icon={<PlusOutlined />}>增加一个在最前面</Button> // 错误展示组件,仅限配合 Form.List 的 rules 一同使用 // Form.List中rules中 1.如果有 message: '错误提示',则会展示 错误提示 // 2.没有则展示 Promise.reject(new Error('至少有两个值'))的 至少有两个值 <Form.ErrorList errors={errors} /> // message: '错误提示' </Form.Item> </> )} </Form.List> <Form.Item> <Button type="primary" htmlType="submit">提交</Button> </Form.Item> </Form> ); };
图片参考
Form.List中rules中有 message属性:
message为错误信息,不设置时会通过模板自动生成。string
Form.List中rules中有 validator属性,无message属性:
validator为自定义校验,接收 Promise 作为返回值。示例参考 (rule, value) => Promise
2.动态增减嵌套字段
嵌套表单字段需要对 field
进行拓展,将 field.name
应用于控制字段
<Form name="form-name-2" onFinish={onFinish} // 关闭输入框自动完成功能 autoComplete="off"> <Form.List name="users"> {(fields, { add, remove }) => ( <> {fields.map(({ key, name, ...restField }) => ( <Space key={key} style={{ display: 'flex', marginBottom: 8 }} align="baseline"> <Form.Item {...restField} name={[name, 'first']} rules={[{ required: true, message: '缺少第一个名字' }]} > <Input placeholder="第一个名字" /> </Form.Item> <Form.Item {...restField} name={[name, 'last']} rules={[{ required: true, message: '缺少最后一个名字' }]} > <Input placeholder="最后一个名字" /> </Form.Item> <MinusCircleOutlined onClick={() => remove(name)} /> </Space> ))} <Form.Item> <Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}> 增加一个 </Button> </Form.Item> </> )} </Form.List> <Form.Item> <Button type="primary" htmlType="submit"> 提交 </Button> </Form.Item> </Form>
3.复杂的动态增减表单项
const areas = [ { label: '成都', value: 'chengdu' }, { label: '雅安', value: 'yaan' }, ]; const sights = { chengdu: ['小学', '中学'], yaan: ['small', 'big'], }; export default function App() { const [form] = Form.useForm(); const onFinish = values => { console.log('表单收取所有的值:', values); }; const handleChange = () => { form.setFieldsValue({ sights: [] }); }; return ( <> <Form form={form} name="form-name-3" onFinish={onFinish} autoComplete="off"> <Form.Item name="area" label="地区" rules={[{ required: true, message: '未输入地区' }]}> <Select options={areas} onChange={handleChange} /> </Form.Item> <Form.List name="sights"> {(fields, { add, remove }) => ( <> {fields.map(field => ( <Space key={field.key} align="baseline"> <Form.Item noStyle // 自定义字段更新逻辑 shouldUpdate={(prevValues, curValues) => prevValues.area !== curValues.area || prevValues.sights !== curValues.sights } > {() => ( <Form.Item {...field} label="景区" name={[field.name, 'sight']} rules={[{ required: true, message: '未输入景区' }]} > <Select disabled={!form.getFieldValue('area')} style={{ width: 130 }}> {(sights[form.getFieldValue('area')] || []).map(item => ( <Option key={item} value={item}> {item} </Option> ))} </Select> </Form.Item> )} </Form.Item> <Form.Item {...field} label="门票价格" name={[field.name, 'price']} rules={[{ required: true, message: '未输入价格' }]} > <Input /> </Form.Item> <MinusCircleOutlined onClick={() => remove(field.name)} /> </Space> ))} <Form.Item> <Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}> 增加一个 </Button> </Form.Item> </> )} </Form.List> <Form.Item> <Button type="primary" htmlType="submit"> 提交 </Button> </Form.Item> </Form> </> ) }
*自定义表单控件 -- 必value、onChange
自定义或第三方的表单控件,也可以与 Form 组件一起使用。只要该组件遵循以下的约定:
-
提供受控属性
value
或其它与valuePropName
的值同名的属性。 -
提供
onChange
事件或trigger
的值同名的事件。
// 必须提供 value、 onChange的组件 const PriceInput = ({ value = {}, onChange }) => { // 如果表单没得默认值,设置的默认值 const [number, setNumber] = useState(0); const [currency, setCurrency] = useState('rmb'); useEffect(() => { console.log('组件', value, onChange); // value与initialValues有联系 // 打印出来 {number: 12, currency: 'rmb'} fn()函数 }, []) // 触发父级form值的更新 const triggerChange = (changedValue) => { onChange?.({ number, currency, ...value, ...changedValue, }); }; const onNumberChange = (e) => { const newNumber = parseInt(e.target.value || '0', 10); // 判断是否不为数字,是数字false,不是为true if (Number.isNaN(number)) { return; } // 主要是为了这个赋值,时刻更新值。如果没得默认值initialValues就会走这里 // value={value.number || number} if (!('number' in value)) { setNumber(newNumber); } triggerChange({ number: newNumber, }); }; const onCurrencyChange = (newCurrency) => { // 如果没得默认值initialValues就会走这里 if (!('currency' in value)) { setCurrency(newCurrency); } triggerChange({ currency: newCurrency, }); }; return ( <span> <Input type="text" value={value.number || number} onChange={onNumberChange} style={{ width: 100, }} /> <Select value={value.currency || currency} style={{ width: 80, margin: '0 8px', }} onChange={onCurrencyChange} > <Option value="rmb">RMB</Option> <Option value="dollar">Dollar</Option> </Select> </span> ); }; const Demo = () => { const onFinish = (values) => { console.log('表单接收的所有值:', values); }; const checkPrice = (_, value) => { if (value.number > 0) { return Promise.resolve(); } return Promise.reject(new Error('价格必须大于零!')); }; return ( <Form name="customized_form_controls" layout="inline" onFinish={onFinish} // 默认 自定义表单的值 initialValues={{ price: { number: 12, currency: 'rmb', }, }} > <Form.Item name="price" label="Price" rules={[ { validator: checkPrice, }, ]} > <PriceInput /> </Form.Item> <Form.Item> <Button type="primary" htmlType="submit"> 提交 </Button> </Form.Item> </Form> ); };
Form.Item下组件的props值
效验
*自定义效验
可以不通过 Form 自己定义校验的时机和内容:
-
validateStatus
: 校验状态,可选 'success', 'warning', 'error', 'validating'。 -
hasFeedback
:用于给输入框添加反馈图标。 -
help
:设置校验文案。
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
hasFeedback | 配合 validateStatus 属性使用,展示校验状态图标,建议只配合 Input 组件使用 | boolean | false |
validateStatus | 校验状态,如不设置,则会根据校验规则自动生成,可选:'success' 'warning' 'error' 'validating' | string | - |
help | 提示信息,如不设置,则会根据校验规则自动生成 | ReactNode | - |
extra | 额外的提示信息,和 help 类似,当需要错误信息和提示文案同时出现时,可以使用这个 | ReactNode | - |
<Form {...formItemLayout} > <Form.Item label="名字" validateStatus="error" help="未输入内容" > <Input placeholder="请输入" id="error" /> </Form.Item> <Form.Item label="地址" validateStatus="warning"> <Input placeholder="请输入" id="warning" prefix={<SmileOutlined />} /> </Form.Item> <Form.Item label="宗教" hasFeedback validateStatus="validating" help="信息正在验证中。。。" > <Input placeholder="请输入" id="validating" /> </Form.Item> <Form.Item label="学校" hasFeedback validateStatus="success"> <Input placeholder="请输入" id="success" /> </Form.Item> </Form>
图片参考
动态校验规则
根据不同情况执行不同的校验规则
export default function App() { const [form] = Form.useForm(); const [checkNick, setCheckNick] = useState(false); // 当选择 效验昵称时候,立即触发表单效验 useEffect(() => { form.validateFields(['nickname']); }, [checkNick]); const onCheckboxChange = (e) => { setCheckNick(e.target.checked); }; const onCheck = async () => { try { const values = await form.validateFields(); console.log('成功:', values); // 成功: {username: '小晴晴', nickname: '超越'} } catch (errorInfo) { console.log('失败:', errorInfo); } }; return ( <> <Form form={form} name="dynamic_rule"> <Form.Item {...formItemLayout} name="username" label="名字" rules={[ { required: true, message: '未输入你的名字', }, ]} > <Input placeholder="请输入你的名字" /> </Form.Item> <Form.Item {...formItemLayout} name="nickname" label="昵称" rules={[ { required: checkNick, message: '未输入你的昵称', }, ]} > <Input placeholder="请输入你的昵称" /> </Form.Item> <Form.Item {...formTailLayout}> <Checkbox checked={checkNick} onChange={onCheckboxChange}> 昵称是否必须 </Checkbox> </Form.Item> <Form.Item {...formTailLayout}> <Button type="primary" onClick={onCheck}> 提交 </Button> </Form.Item> </Form> </> ) }
*自行处理表单数据
Form
具有自动收集数据并校验的功能,但如果您不需要这个功能,或者默认的行为无法满足业务需求,可以选择自行处理数据
结合实际情况来整理出多种 validateStatus、help
import React, { useState } from 'react'; import { Form, InputNumber } from 'antd'; function validatePrimeNumber(number) { if (number === 11) { return { validateStatus: 'success', errorMsg: null, }; } return { validateStatus: 'error', errorMsg: '8到12之间的素数是11!', }; } const formItemLayout = { labelCol: { span: 7, }, wrapperCol: { span: 12, }, }; export default function App() { const [number, setNumber] = useState({ value: 11, }); const tips = '素数是一个大于1的自然数,除1和它本身外,没有其他正因子'; const onNumberChange = (value) => { setNumber({ ...validatePrimeNumber(value), value }); }; return ( <> <Form> <Form.Item {...formItemLayout} label="8点到12点之间" validateStatus={number.validateStatus} help={number.errorMsg || tips} > <InputNumber min={8} max={12} value={number.value} onChange={onNumberChange} /> </Form.Item> </Form> </> ) }
FormInstance -- form实例
// 设置表单的值(该值将直接传入 form store 中。如果你不希望传入对象被修改,请克隆后传入) form.setFieldsValue({ note: '你好', gender: '男性',}); // 重置一组字段到 initialValues form.resetFields() // 获取对应字段名的值 // 获取一组字段名对应的值,会按照对应结构返回。默认返回现存字段值 form.getFieldValue('username') form.getFieldsValue(['username', 'password']) // 返回是 {username: 'chaoyue', password: 'xxx123'} form.getFieldsValue(true) // 获取所有的值 form.getFieldsValue(['note', 'gender'], (meta) => {return true}) // return true时才能获取值 // 检查对应字段是否被用户操作过 // 检查一组字段是否被用户操作过。 form.isFieldTouched('username') form.isFieldsTouched(true) form.isFieldsTouched(['username', 'password'], true) // 第二属性 为 true 时检查是否所有字段都被操作过,如果不写,但凡有一个操作就为true // 获取对应字段名的错误信息 // 获取一组字段名对应的错误信息,返回为数组形式。(nameList?: NamePath[]) => FieldError[] form.getFieldError('username') form.getFieldsError() // 提交表单,与点击 submit 按钮效果相同 form.submit() // 触发表单验证。(nameList?: NamePath[]) => Promise form.validateFields(['nickname']); // 单独验证一个表单 form.validateFields(); // 验证所有表单 const onCheck = async () => { try { const values = await form.validateFields(); console.log('Success:', values); } catch (errorInfo) { console.log('Failed:', errorInfo); } }; // 获取对应字段实例,比如 Input实例 form.getFieldInstance('username') // 检查对应字段是否正在校验,返回 true 与 false form.isFieldValidating('username') // 设置一组字段状态 form.setFields([{ errors: ['请输入'], name: ['username'], touched: false, validating: false, value: '程超越', }])
NamePath
string | number | (string | number)[]
FieldData
名称 | 说明 | 类型 |
---|---|---|
errors | 错误信息 | string[] |
name | 字段名称 | NamePath[] |
touched | 是否被用户操作过 | boolean |
validating | 是否正在校验 | boolean |
value | 字段对应值 | any |
form.getFieldsValue
console.log(form.getFieldsValue(['note', 'gender'], (meta) => { console.log('111', meta); return false // return true }));
1、return false的情况
2、return true的情况。form.getFieldsValue(xxx)才有结果,获取值
form.getFieldsError()
form.getFieldInstance('username')
validateMessages -- 默认报错信息
name
属性支持嵌套数据结构。通过 validateMessages
或 message
自定义校验信息模板
const typeTemplate = "'${name}' is not a valid ${type}"; export const defaultValidateMessages = { default: "Validation error on field '${name}'", required: "'${name}' is required", enum: "'${name}' must be one of [${enum}]", whitespace: "'${name}' cannot be empty", date: { format: "'${name}' is invalid for format date", parse: "'${name}' could not be parsed as date", invalid: "'${name}' is invalid date", }, types: { string: typeTemplate, method: typeTemplate, array: typeTemplate, object: typeTemplate, number: typeTemplate, date: typeTemplate, boolean: typeTemplate, integer: typeTemplate, float: typeTemplate, regexp: typeTemplate, email: typeTemplate, url: typeTemplate, hex: typeTemplate, }, string: { len: "'${name}' must be exactly ${len} characters", min: "'${name}' must be at least ${min} characters", max: "'${name}' cannot be longer than ${max} characters", range: "'${name}' must be between ${min} and ${max} characters", }, number: { len: "'${name}' must equal ${len}", min: "'${name}' cannot be less than ${min}", max: "'${name}' cannot be greater than ${max}", range: "'${name}' must be between ${min} and ${max}", }, array: { len: "'${name}' must be exactly ${len} in length", min: "'${name}' cannot be less than ${min} in length", max: "'${name}' cannot be greater than ${max} in length", range: "'${name}' must be between ${min} and ${max} in length", }, pattern: { mismatch: "'${name}' does not match pattern ${pattern}", }, };
Form 为验证提供了默认的错误提示信息,你可以通过配置 validateMessages
属性,修改对应的提示模板。一种常见的使用方式,是配置国际化提示信息:
const validateMessages = { required: "'${name}' 是必选字段", types: { email: '${label} is not a valid email!', number: '${label} is not a valid number!', }, number: { range: '${label} must be between ${min} and ${max}', }, }; <Form validateMessages={validateMessages} />;
此外,ConfigProvider 也提供了全局化配置方案,允许统一配置错误提示模板:
const validateMessages = { required: "'${name}' 是必选字段", // ... }; <ConfigProvider form={{ validateMessages }}> <Form /> </ConfigProvider>;
Form.Provider -- 多个form
提交表单 -- htmlType="submit"和form.submit
通过 Form.Provider
在表单间处理数据。本例子中,Modal 的确认按钮在 Form 之外,通过 form.submit
方法调用表单提交功能。反之,则推荐使用 <Button htmlType="submit" />
调用 web 原生提交逻辑。
提供表单间联动功能,其下设置 name
的 Form 更新时,会自动触发对应事件。
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
onFormChange | 子表单字段更新时触发 | function(formName: string, info: { changedFields, forms }) | - |
onFormFinish | 子表单提交时触发 | function(formName: string, info: { values, forms }) | - |
<Form.Provider onFormFinish={name => { if (name === 'form1') { // Do something... } }} > <Form name="form1">...</Form> <Form name="form2">...</Form> </Form.Provider>
案例
// 当模式为“窗体,关闭”时重置窗体字段 const useResetFormOnCloseModal = ({ form, visible }) => { const prevVisibleRef = useRef(); useEffect(() => { prevVisibleRef.current = visible; }, [visible]); const prevVisible = prevVisibleRef.current; useEffect(() => { if (!visible && prevVisible) { form.resetFields(); } }, [visible]); }; const ModalForm = ({ visible, onCancel }) => { const [form] = Form.useForm(); useResetFormOnCloseModal({ form, visible, }); const onOk = () => { form.submit(); }; return ( <Modal title="添加表" visible={visible} onOk={onOk} onCancel={onCancel}> <Form form={form} layout="vertical" name="userForm"> <Form.Item name="name" label="用户名" rules={[ { required: true, }, ]} > <Input /> </Form.Item> <Form.Item name="age" label="用户年龄" rules={[ { required: true, }, ]} > <InputNumber /> </Form.Item> </Form> </Modal> ); }; export default function App() { const [visible, setVisible] = useState(false); const showUserModal = () => { setVisible(true); }; const hideUserModal = () => { setVisible(false); }; const onFinish = (values) => { console.log('Finish:', values); }; return ( <Form.Provider onFormFinish={(name, { values, forms }) => { if (name === 'userForm') { const { basicForm } = forms; console.log('111', basicForm.getFieldValue('users'), values ); // 打印为 undefined {name: 123, age: 25} const users = basicForm.getFieldValue('users') || []; basicForm.setFieldsValue({ users: [...users, values], }); setVisible(false); } }} > <Form {...layout} name="basicForm" onFinish={onFinish}> <Form.Item name="group" label="组的名称" rules={[ { required: true, }, ]} > <Input /> </Form.Item> <Form.Item label="用户列表" shouldUpdate={(prevValues, curValues) => prevValues.users !== curValues.users} > {({ getFieldValue }) => { const users = 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 {...tailLayout}> <Button htmlType="submit" type="primary"> 提价 </Button> <Button htmlType="button" style={{ margin: '0 8px', }} onClick={showUserModal} > Add User </Button> </Form.Item> </Form> <ModalForm visible={visible} onCancel={hideUserModal} /> </Form.Provider> ) }
Upload上传
function beforeUpload(file) { // 文件类型 const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png'; if (!isJpgOrPng) { message.error('You can only upload JPG/PNG file!'); } // 文件大小 const isLt2M = file.size / 1024 / 1024 < 2; if (!isLt2M) { message.error('Image must smaller than 2MB!'); } return isJpgOrPng && isLt2M; } const props = { name: 'file', action: 'https://www.mocky.io/v2/5cc8019d300000980a055e76', // 设置上传的请求头部,IE10 以上有效 headers: { authorization: 'authorization-text', }, // 上传文件改变时的状态,详见 onChange onChange(info) { if (info.file.status !== 'uploading') { console.log(info.file, info.fileList); } if (info.file.status === 'done') { message.success(`${info.file.name} file uploaded successfully`); } else if (info.file.status === 'error') { message.error(`${info.file.name} file upload failed.`); } }, }; ReactDOM.render( <Upload {...props} // 上传文件之前的钩子,参数为上传的文件,若返回 false 则停止上传。 // 支持返回一个 Promise 对象,Promise 对象 reject 时则停止上传,resolve 时开始上传( resolve 传入 File 或 Blob 对象则上传 resolve 传入对象);也可以返回 Upload.LIST_IGNORE,此时列表中将不展示此文件。 注意:IE9 不支持该方法 beforeUpload={beforeUpload} // 通过覆盖默认的上传行为,可以自定义自己的上传实现 customRequest={function} > <Button icon={<UploadOutlined />}>点击上传</Button> </Upload>, mountNode, );
onChange
上传中、完成、失败都会调用这个函数。
文件状态改变的回调,返回为:
{ file: { /* ... */ }, fileList: [ /* ... */ ], event: { /* ... */ }, }
file
当前操作的文件对象
{ uid: 'uid', // 文件唯一标识,建议设置为负数,防止和内部产生的 id 冲突 name: 'xx.png' // 文件名 status: 'done', // 状态有:uploading done error removed,被 beforeUpload 拦截的文件没有 status 属性 response: '{"status": "success"}', // 服务端响应内容 linkProps: '{"download": "image"}', // 下载链接额外的 HTML 属性 }
fileList
当前的文件列表
event
上传中的服务端响应内容,包含了上传进度等信息,高级浏览器支持
input -- type="file"
accept
该accept
属性值是定义文件类型文件输入应该接受一个字符串。此字符串是以逗号分隔的唯一文件类型说明符列表。因为给定的文件类型可能以多种方式标识,所以当您需要给定格式的文件时,提供一组完整的类型说明符很有用。
例如,有多种方法可以识别 Microsoft Word 文件,因此接受 Word 文件的站点可能会使用<input>
如下方式:
<input type="file" id="docpicker" accept=".doc,.docx,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document">
唯一的文件类型说明符
是描述类型的文件可以由用户在被选择的字符串[`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input)类型的元件
file`。每个唯一的文件类型说明符可能采用以下形式之一:
-
有效的不区分大小写的文件扩展名,以句点 (".") 字符开头。例如:
.jpg
、.pdf
、 或.doc
。 -
一个有效的 MIME 类型字符串,没有扩展名。
-
字符串,
audio/*
意思是“任何音频文件”。 -
字符串,
video/*
意思是“任何视频文件”。 -
字符串,
image/*
意思是“任何图像文件”。
该accept
属性将包含一个或多个这些唯一文件类型说明符的字符串作为其值,以逗号分隔。例如,需要可以作为图像呈现的内容(包括标准图像格式和 PDF 文件)的文件选择器可能如下所示:
<input type="file" accept="image/*,.pdf">
*beforeUpload -- 上传文件之前处理
1.上传文件之前的钩子,参数为上传的文件,若返回 false
则停止上传。支持返回一个 Promise 对象,Promise 对象 reject 时则停止上传,resolve 时开始上传( resolve 传入 File
或 Blob
对象则上传 resolve 传入对象);也可以返回 Upload.LIST_IGNORE
,此时列表中将不展示此文件。 注意:IE9 不支持该方法
2.beforeUpload
返回 false
或 Promise.reject
时,只用于拦截上传行为,不会阻止文件进入上传列表(原因)。如果需要阻止列表展现,可以通过返回 Upload.LIST_IGNORE
实现。
const validateFileFormat(name) { const ext = name.split('.').pop() switch (this.mode) { // this.mode === 1或2 case 1: return ['jpg', 'jpeg', 'png', 'gif', 'bmp'].indexOf(ext.toLOwerCase()) !== -1 case 2: return true default: return false } } const validateFileSize(name, size) { if (size > 5 * 1024 * 1024) { return false } return true } const beforeUploadFile = (file, fileList) => { // 只上传png const isPNG = file.type === 'image/png'; if (!isPNG) { message.error(`${file.name} 不是png`); } return isPNG || Upload.LIST_IGNORE; // || 第一个为假就返回第二个 // 限制上传数量 if (fileList.length >= 3) { message.error('最多只能上传3个'); return false } // 限制文件类型 if (!validateFileFormat(file.name)) { message.error('文件格式不合法'); return false } // 限制文件大小 if (!validateFileSize(file.name, file.size)) { message.error('上传文件过大,不能超过5M'); return false } console.log('beforeUploadFile事件', file, fileList); }
*onChange -- 上传文件改变时的状态
上传中、完成、失败都会调用这个函数。
const onchangeFile = (info) => { // loading情况 if (info.file.status === 'uploading') { setState({ loading: true }); return; } // 完成后关闭加载状态 if (info.file.status === 'done') { setState({ loading: false }); ); // 上传失败 if (info.file.status === 'error') { message.error(`${info.file.name} 文件上传失败`); } // 限制上传文件的数量只显示两个最近上传的文件,旧文件将替换为新文件(上传列表数量的限制) let fileList = [...info.fileList]; fileList = fileList.slice(-2); // 读取响应并显示文件链接(读取远程路径并显示链接) fileList = fileList.map(file => { if (file.response) { // 组件将显示文件。url作为链接 file.url = file.response.url; } return file; }); this.setState({ fileList }); console.log('上传中、完成、失败都会调用这个函数', info); }
文件状态改变的回调,返回为:
{ file: { /* ... */ }, fileList: [ /* ... */ ], event: { /* ... */ }, }
-
file
当前操作的文件对象。{ uid: 'uid', // 文件唯一标识,建议设置为负数,防止和内部产生的 id 冲突 name: 'xx.png' // 文件名 status: 'done', // 状态有:uploading done error removed,被 beforeUpload 拦截的文件没有 status 属性 response: '{"status": "success"}', // 服务端响应内容 linkProps: '{"download": "image"}', // 下载链接额外的 HTML 属性 }
-
fileList
当前的文件列表。 -
event
上传中的服务端响应内容,包含了上传进度等信息,高级浏览器支持。
*itemRender -- 自定义上传列表项
(originNode: ReactElement, file: UploadFile, fileList: object[], actions: { download: function, preview: function, remove: function }) => React.ReactNode
const handleItemRender = (originNode, file, fileList, actions) => { // console.log('自定义上传列表项', originNode, file, fileList, actions); // const errorNode = <Tooltip title="上传失败">{originNode.props.children}</Tooltip>; if (file.status === 'done') { return <Tooltip title="上传成功"> <div className='ant-upload-list-item ant-upload-list-item-done ant-upload-list-item-list-type-text'> <div className='ant-upload-list-item-info'> <span className='ant-upload-span'> {originNode.props.children[0].props.children.props.children[0]} <span style={{ padding: '0 8px', fontSize: '14px' }}>{file.name}</span> <a onClick={reUpload}>重新上传</ a> </span> </div> </div> </Tooltip>; } else if (file.status === 'error') { return <Tooltip title="上传失败"> <div className='ant-upload-list-item ant-upload-list-item-error ant-upload-list-item-list-type-text'> <div className='ant-upload-list-item-info'> <span className='ant-upload-span'> {originNode.props.children.props.children[0].props.children.props.children[0]} {originNode.props.children.props.children[0].props.children.props.children[1][0]} <span className='ant-upload-list-item-card-actions' style={{ display: 'flex', alignItems: 'center' }}><ReloadOutlined style={{ cursor: 'pointer' }} /></span> {originNode.props.children.props.children[0].props.children.props.children[1][1]} </span> </div> </div> </Tooltip>; } else { return (originNode) } }
*customRequest -- 自定义上传
允许通过覆盖 AjaxUploader 中的默认行为进行高级自定义。提供您自己的 XMLHttpRequest 调用以与自定义后端进程交互或通过 aws-sdk-js 包与 AWS S3 服务交互。
customRequest 回调传递了一个对象:
-
onProgress: (event: { percent: number }): void
-
onError: (event: Error, body?: Object): void
-
onSuccess: (body: Object): void
-
data: Object
-
filename: String
-
file: File
-
withCredentials: Boolean
-
action: String
-
headers: Object
const handleRef = useRef({}) const handleCustomRequest = ({ onProgress, onError, onSuccess, data, filename, withCredentials, file, action, headers }) => { // 保存当前的自定义上传 const { uid } = file handleRef.current[uid] = { onError, onProgress, onSuccess, } const fileData = new FormData() fileData.append('file', file) const xhr = new XMLHttpRequest() // 携带cookie xhr.withCredentials = true // 进度条 xhr.upload.addEventListener('progress', (e) => { if (e.lengthComputable) { var percentComplete = e.loaded / e.total * 100 onProgress({ percent: percentComplete }) } }) // 小黄人加载成功 xhr.addEventListener('load', (e) => { if (xhr.readyState === 4 && xhr.status === 200) { const res = JSON.parse(xhr.responseText) if (res.data && res.success) { const copy = [...audioFile] copy.forEach((f) => { if (f.name === file.name) { i.ossFileKey = res.data } }) onSuccess({ ...res, ossFileKey: res.data, }) } else { onError({ status: 'error', }, file) } } else { onError({ status: 'error', }, file) } }) // 小黄人加载失败 xhr.addEventListener('error', (e) => { onError({ status: 'error', }, file) }) xhr.open('POST', 'https://www.mocky.io/v2/5cc8019d300000980a055e76', true) xhr.send(fileData) return { abort() { console.log('上载进程被终止') } } }
1.onError: (event: Error, body?: Object)
2.onSuccess: (body: Object)
3.onProgress: (event: { percent: number })
其他相关属性
maxCount -- 限制上传数量
showUploadList -- 设置列表交互图标
withCredentials -- 上传请求时是否携带 cookie
是否展示文件列表, 可设为一个对象,用于单独设定 showPreviewIcon
, showRemoveIcon
, showDownloadIcon
, removeIcon
和 downloadIcon
boolean | { showPreviewIcon?: boolean, showRemoveIcon?: boolean, showDownloadIcon?: boolean, previewIcon?: ReactNode | (file: UploadFile) => ReactNode, removeIcon?: ReactNode | (file: UploadFile) => ReactNode, downloadIcon?: ReactNode | (file: UploadFile) => ReactNode }
<Upload name="logo" action="https://www.mocky.io/v2/5cc8019d300000980a055e76" headers={{ authorization: 'authorization-text', }} showUploadList={{ showDownloadIcon: false, downloadIcon: 'download ', showRemoveIcon: true, removeIcon: <StarOutlined onClick={e => console.log(e, '被点击了')} />, }} // 当为 1 时,始终用最新上传的代替当前 maxCount={1} withCredentials={false} > <Button icon={<UploadOutlined />}>上传</Button> </Upload>