react--antd(4.x)开发一定会踩的坑

2,689 阅读5分钟

一: 表单验证:

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

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

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

两个验证规则。原本的目的是在没有输入任何值时只提示“手机号不能为空”,结果同时出现“请输入正确的手机号”“手机号格式错误”两个报错。也就是validator优先级最高。所以解决办法有两种:

(1)所有的验证放在valiator中包括为空的情况

(2)不要valiator函数,而是把为空和验证规则拆开,如下:


                <Form.Item
                    name="account"
                    validateTrigger="onBlur"
                    rules={
                        [
                            {   required: true,
                                message: '手机号不能为空'
                            },
                            {
                                 pattern:/^1(3|4|5|6|7|8|9)\d{9}$/g,
                                 message:"请输入正确的手机号"
                             },
                            
                        ]
                    }     
                >
                    <Input  size="large" max={11} value={account} allowClear  prefix={<IconFont type="icon-zhanghao"  />}/>
                </Form.Item>

2:设置表单默认值问题:

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


function CreateCatagory(props){

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

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


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

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

export default CreateCatagory

结果报错: ​

​ 顺着报错,initialValues看了下antd的官网有这样一段描述“”

  1. 不再需要也不应该用 onChange 来做数据收集同步(你可以使用 Form 的 onValuesChange),但还是可以继续监听 onChange 事件。
  2. 你不能用控件的 value 或 defaultValue 等属性来设置表单域的值,默认值可以用 Form 里的 initialValues 来设置。注意 initialValues 不能被 setState 动态更新,你需要用 setFieldsValue 来更新。
  3. 你不应该用 setState,可以使用 form.setFieldsValue 来动态改变表单值 所以做了一个尝试:代码改动如下:
import {Modal,Form,Input,Button,Space,Select,Radio,Tabs,Checkbox  } from 'antd';
import { useState ,useRef, useEffect} from 'react';
import "./index.less"
import Iconfont from 'components/iconfont';
const list=[
        {id:"0001",name:"a属性"},
        {id:"0002",name:"b属性"},
        {id:"0003",name:"c属性"},
        {id:"0004",name:"d属性"},
        {id:"0005",name:"e属性"},
        {id:"0006",name:"f属性"},
    ]
    // const catagoryPositions=["一级类目","二级类目"].join("/")
const catagoryPositions=["一级类目","二级类目"]

function CreateCatagory(props){

    const  attrSettingDialogRef=useRef()
    const [isLastCatagoryVal,setIsLastCatagoryVal]=useState(1)
    const [setttingVal,setSetttingVal]=useState(1)
    const [isShowPeroptyRow,setIsShowPeroptyRow]=useState(true)
    
    const formInitialValues=({
        inherit:1
    })

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


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

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

export default CreateCatagory

通过表单本身去进行setFieldsValue操作。把defaultValue的逻辑改为表单的操作。终于可以正常显示。

注意改动的地方:

(1)form表单本身绑定initialValues={formInitialValues} 

(2)定义一个初始值

  const formInitialValues=({

        inherit:1,

        isLastCatagory:1

    })

(3)初始化根据其他值更改其默认值

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

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

如图:我们想点击勾选的时候,把当前的数据打印出来 ​

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

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

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

四。如何取消Modal.confirm的onCancel事件?

业务开发中遇到这么一个需求。点击按钮弹出确认框。点击确认按钮以后需要再出现一个弹窗。暂且叫它弹窗B,当点击弹窗B的关闭按钮时,发现confirm已经不存在了?这个问题很诡异。找了很多办法,终于解决了这个问题:

(方案一:)

在antd的Modal.confirm-api中对onCancel和onOk的描述:

onCancel取消回调,参数为关闭函数,返回 promise 时 resolve 后自动关闭function(close)-
onOk点击确定回调,参数为关闭函数,返回 promise 时 resolve 后自动关闭

也就是说该函数的参数是一个函数。如果我们不让其返回一个promise,就可以阻止它的默认行为。

接下来我们进行改造:

const delCatgory= ()=>{
            Modal.confirm({
                title:"确认删除改属性",
                closable:true,
                width:420,
                okText:"确认",
                cancelText:"取消",
                 onOk:(close)=>{
                     delModalRef.current.showModelRef() //这里通过ref直接操作弹窗B的显示
                 },
                 onCancel:(close)=>{
                     return false
                 }
            })
//注意这里onok和onCancel里传入了close函数。这样就可以再关闭弹窗B的时候让confirm依然存在
方案二:)通过自定义内容,隐藏页脚的按钮,然后通过自定义内容添加确认和取消两个按钮
   const delCatgory= ()=>{
            Modal.confirm({
                title:"确认删除改属性",
                closable:true,
                width:420,
                okText:"",
                cancelText:"",
                content: <>
                    <p>'执行删除类目,子级类目将同步被删除,确认删除吗?'</p>
                    <div>
                         <Button  onClick={confirmDelAttrModal}>确定</Button>
                         <Button onClick={cancelDelAttrModal}>取消</Button>
                    </div>
                </>,
            })
        }
 //剩下的就是对Modal.confirm默认的底部按钮通过样式进行了隐藏

五。checkbox.Group通过options传递数据。如何精准的控制单个checkbox的状态? 如下图:勾选checkbox的时候同步显示标签。对应的checkbox同步状态更改 ​

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

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

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


function Dialog(props, ref) {

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



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

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

  const handleClose = () => {
    hideModal();
    let num=100;
    console.log("close")
    confirmFn(num)
    
  }
  const selectedItem = (values) => {
        console.log("values:",values)
        console.log("arr:",arr)
       
        setArr(values)
        console.log("b--:",b)
        let b=[];
        let temp=values
        list.forEach(item=>{
            for(let j=0;j<temp.length;j++){
                if(item.value===temp[j]){
                    b.push(item)
                }
            }
        })
        setCheckedList(b)
  }
  const closeTag=(tag)=>{
    console.log("closeTag-tag-:",tag)
    let tags=checkedList.filter(item=>item.value!==tag.value)
    let res=[];
    tags.forEach(item=>res.push(item.value))
    setArr(res)
    setCheckedList(tags)
  }
  
  return (   
      <Modal
        icon=""
        title="设置属性值"
        width={670}
        footer={null}
        visible={visible}
        centered={true}
        maskClosable={false}
        onCancel={handleClose}
        destroyOnClose={true}
      >
          <div className='create-catagory-attrs-modal'>         
            { <Checkbox.Group
                onChange={selectedItem}
                value={arr}
                options={list}
              >               
             </Checkbox.Group>
            }
            <div className="tag-box">
                {
                    checkedList.map(item=>(
                        <Tag closable onClose={()=>closeTag(item)}>
                            {
                                item.label
                            }
                        </Tag>
                    ))
                }
            </div>
        </div>
      </Modal>
  )
}
export default forwardRef(Dialog)

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

六:hooks中对数组push操作useState无效?

​ 如图。每次点击添加后台类目的时候展示一条数据

原始操作:


  const [types,setTypes]=useState([])

  const addType=()=>{
    
    temp.push(type);
    console.log("temp:",temp)
    setTypes(temp)
}

<Form.Item label="支持后台类目" name="type" rules={[{ required: true, message: '显示位置不能为空' }]}>
            <div>
                {
                    types.map((item,index)=><div key={index}>{item}</div>)
                }
            </div>
            <div>
                <Button size='small' onClick={addType}>+添加后台类目</Button>
            </div>
 </Form.Item>

此时temp打印发现:数组types已经是最新的数据。但是在setTypes这步操作完成以后发现完全没有任何反应。后来经过查阅资料发现。useState对于复杂数据类型的更新条件是两个不同的内存地址,如果是对同一个引用是无效的。所以改成这样:

  const addType=()=>{
        let type=`第${Math.random()*100+10}个类目`
        let temp=JSON.parse(JSON.stringify(types))
        temp.push(type);
        setTypes(temp)
    }

通过深拷贝得到一份新的引用地址。这时候setTypes正常更新

七:antd表格如何实现全选,如何在表格以外去控制表格全选? ​**​**​ 今天碰到这样一个需求:在表格之外通过一个按钮去控制表格的全选。由于对antd组件库不熟悉。导致走了很多弯路:

antd里如何实现基本的全选的?

官方的demo文档里给了一个简单的demo

const columns = [
    {
      title: 'Name',
      dataIndex: 'name',
      
    },
    {
      title: 'Age',
      dataIndex: 'age',
    },
    {
      title: 'Address',
      dataIndex: 'address',
    },
  ];
  
  const data = [];
  for (let i = 0; i < 10; i++) {
    data.push({
      key: i,
      name: `天然气安装 ${i}`,
      code: `00000-${i}`,
       unit: `米`,
       price:2349.00,
       fuliao:2,
       status:`${i}`,
       mark:"xxxxxxxxxxx",
       operate:null
    });
  }
  const rowSelection = {
    onChange: (selectedRowKeys, selectedRows) => {
      if(selectedRowKeys.length===data.length){
        setSelectedRowKeys(selectedRowKeys)
        setIsCheckeAll(true)
      }else{
        setSelectedRowKeys([])
        setIsCheckeAll(false)
      }
    },
    selectedRowKeys,
    getCheckboxProps: (record) => ({
      disabled: record.name === 'Disabled User',
      name: record.name,
    }),
  };

 <Table  
                                       
       hideSelectAll={true}
       rowSelection={rowSelection} 
        columns={columns} 
        dataSource={data}
 >
</Table>

没错。就是这个rowSelection,因为添加了这个属性。所以table头部的复选框能实现选中全部勾选,取消勾选。全部不选中。经过进一步的摸索。发现api里还有这么一句话:

selectedRowKeys指定选中项的 key 数组,需要和 onChange 进行配合

\

经过尝试发现。它就是解决这个问题的关键。table全选的状态。完全依靠这个数组。这个数组是当前数据的key 的集合。如果勾选了两条数据。那么这个selectedRowKeys的值就是这两条数据的key 的集合。如果勾选了表格所有的行。那么这个selectedRowKeys就说整个data的key的集合。知道了这个关键先生。接下来我们大胆的尝试。给表格外面checkbox的checked绑定一个值。true表示全选(勾选)。此时我们把data的key全部取出来赋值给selectedRowKeys,就能达到目的,

 const columns = [
    {
      title: 'Name',
      dataIndex: 'name',
      
    },
    {
      title: 'Age',
      dataIndex: 'age',
    },
    {
      title: 'Address',
      dataIndex: 'address',
    },
  ];
  
  const data = [];
  for (let i = 0; i < 10; i++) {
    data.push({
      key: i,
      name: `天然气安装 ${i}`,
      code: `00000-${i}`,
       unit: `米`,
       price:2349.00,
       fuliao:2,
       status:`${i}`,
       mark:"xxxxxxxxxxx",
       operate:null
    });
  }
function TypeProdPage(){

  const router=useHistory()
  const [isCheckedAll,setCheckAll]=useState(false)
  const [selectedRowKeys,setSelectedRowKeys]=useState([])
  const [isCheckeAll,setIsCheckeAll]=useState(false)

  const onSelect=()=>{

  }
  const onCheck=()=>{
      
  }
  const selectAll=(e)=>{
    if(e.target.checked){
      let keys=[];
      data.forEach(i=>keys.push(i.key))
      setSelectedRowKeys(keys) //这里是关键
    }else{
      setSelectedRowKeys([])
    }
    setIsCheckeAll(e.target.checked)
  }
  const rowSelection = {
    onChange: (selectedRowKeys, selectedRows) => {
      if(selectedRowKeys.length===data.length){
        setSelectedRowKeys(selectedRowKeys)
        setIsCheckeAll(true)  //同步更改表格外面checkbox的状态
      }else{
        setSelectedRowKeys(selectedRowKeys)
        setIsCheckeAll(false)  //同步更改表格外面checkbox的状态
      }
    },
    selectedRowKeys,
    getCheckboxProps: (record) => ({
      disabled: record.name === 'Disabled User',
      name: record.name,
    }),
  };
  const renderTitle=(data)=>{
      console.log("data:",data)
      return <div className="tree-item">
          <div className="tree-txt">{data.title}</div>
          <div className="tree-oper-btn">a</div>
      </div>
  }

    

    return (
        <div className="type-prod-page">
            <Tabs defaultActiveKey="1" size="large">
                {
                    combo.map(item=>
                        ( <TabPane tab={item.name} key={item.id} >
                                <div className="tab-container">
                                    <div className="tree-menu">
                                        <Tree
                                            defaultExpandParent
                                            onSelect={onSelect}
                                            treeData={treeData}
                                            titleRender={
                                                renderTitle
                                            }
                                        />
                                    </div>
                                    <div className="content">
                                    <Table  
                                        title={(data) => 
                                          <div className="table-title">
                                            <Row>
                                                <Col span={12}>
                                                    <Checkbox onChange={(e)=>selectAll(e)} checked={isCheckeAll}>全选</Checkbox>
                                                    已选<span>2</span>个对象,共1200个对象
                                                </Col>
                                                <Col span={12}>
                                                    <Space >
                                                        <Button type="primary">添加定额</Button>
                                                        <Button type="primary">编辑</Button>
                                                        <Button type="primary">移除</Button>
                                                    </Space>
                                                </Col>
                                            </Row>
                                          </div>
                                      }
                                      hideSelectAll={true}
                                      rowSelection={rowSelection} 
                                      columns={columns} 
                                      dataSource={data}
                                    >
                                    </Table>
                                    </div>
                                </div>
                            </TabPane>
                        )
                )
            }
            </Tabs>
        </div>
    )

antd很优秀但是其官网文档太草率 更多记录。持续更新中...