Ant Design

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) => booleanfalse
name -- 受控

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

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

2.你不能用控件的 valuedefaultValue 等属性来设置表单域的值,默认值可以用 Form 里的 initialValues 来设置。注意 initialValues 不能被 setState 动态更新,你需要用 setFieldsValue 来更新。

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

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
    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) => voidinsertIndex
move移动表单项(from: number, to: number) => void-
remove删除表单项(index: number | number[]) => voidnumber[]
  • 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 自己定义校验的时机和内容:

  1. validateStatus: 校验状态,可选 'success', 'warning', 'error', 'validating'。

  2. hasFeedback:用于给输入框添加反馈图标。

  3. help:设置校验文案。

参数说明类型默认值
hasFeedback配合 validateStatus 属性使用,展示校验状态图标,建议只配合 Input 组件使用booleanfalse
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 属性支持嵌套数据结构。通过 validateMessagesmessage 自定义校验信息模板

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 传入 FileBlob 对象则上传 resolve 传入对象);也可以返回 Upload.LIST_IGNORE,此时列表中将不展示此文件。 注意:IE9 不支持该方法

2.beforeUpload 返回 falsePromise.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: { /* ... */ },
}
  1. file 当前操作的文件对象。

    {
       uid: 'uid',      // 文件唯一标识,建议设置为负数,防止和内部产生的 id 冲突
       name: 'xx.png'   // 文件名
       status: 'done', // 状态有:uploading done error removed,被 beforeUpload 拦截的文件没有 status 属性
       response: '{"status": "success"}', // 服务端响应内容
       linkProps: '{"download": "image"}', // 下载链接额外的 HTML 属性
    }
  2. fileList 当前的文件列表。

  3. 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, removeIcondownloadIcon

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>

  • 26
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值