AntD 打怪筆記

前言

本文用於記錄在使用andt相關組件時的採坑合集,挖掘一下組件的能力邊界javascript

Form使用指南

1. 雙向綁定數據到this.props.form

關鍵詞: getFieldDecorator
使用getFieldDecorator包裝過的字段會自動和表單進行雙向綁定,而且只有getFieldDecorator包裝過得字段纔會有 getFieldsValue getFieldValue setFieldsValue
demo:
<FormItem
  {...formItemLayout}
  label='name'}
>
  {getFieldDecorator('name', {})(
    <Input /> )} </FormItem>
複製代碼
字段name這時會與this.props.form.name雙向綁定,不須要onChange去顯式地控制值,在submit的時候,全部包裝過得字段都會出如今form的屬性裏。
不過若是想實現表單的聯動,只能經過onChange和this.props.form.setFieldsValue去控制別的字段,我的以爲不夠優雅,若是能與state直接綁定會清爽不少。


注意:
  1. 一個 Form.Item 建議只放一個被 getFieldDecorator 裝飾過的 child,當有多個被裝飾過的 child 時, help required validateStatus 沒法自動生成。
  2. 2.2.0 以前,只有當表單域爲 Form.Item 的子元素時,纔會自動生成 help required validateStatus,嵌套狀況須要自行設置。
  3. 不能再用value/defaultValue來設置表單的值,只能用initialValue設置。
  4. 不能裝飾純組件,畢竟是修飾器。。。


2. 將表單數據與上層組件交互

關鍵詞:mapPropsToFields, onFieldsChange, onValuesChange
mapPropsToFields: 把父組件的屬性映射到表單項上(可用於把 Redux store 中的值讀出)
onFieldsChange: 當 Form.Item 子節點的值發生改變時觸發,能夠把對應的值轉存到 Redux store
demo:
@Form.create({
  mapPropsToFields(props) {
    // 使用上層組件的scope的值做爲表單的數據
    const { scope } = props;

    return {
      nickname: Form.createFormField({
        value: scope.nickname,
      }),
      phone: Form.createFormField({
        value: scope.phone,
      }),
      address: Form.createFormField({
        value: scope.address,
      }),
      agreement: Form.createFormField({
        value: scope.agreement,
      }),
    };
  },
  onValuesChange(props, values) {
    // 將表單的變化值回填到上層組件的scope中
    props.onFormChange(values);
  },
})
複製代碼
這樣就提供了與上層組件交互的能力,對於數據在store中的場景頗有用。


3. 豐富的校驗

關鍵詞: rules
demo:
<FormItem
  {...formItemLayout}
  label='text'
  extra={this.confirmNode()}
>
  {getFieldDecorator('subscribe', {
    initialValue: subscribe,
    rules: [{
      required: true,
      message: ‘message’,
    }],
  })(
    <Receiver subscribe={subscribe} onReceiverChange={this.onReceiverChange} /> )} </FormItem>
複製代碼
可使用現有的校驗規則方便地設置字段的規則


4. 自定義校驗

關鍵詞: validateFields/validateFieldsAndScroll
demo:
checkFormConfig = (rule, value, cb) => {
    if (value.length === 0) {
      cb('規則不能爲空');
    }
}
...
<FormItem >
  {getFieldDecorator('config', {
    initialValue: 'init value',
    rules: [{
      validator: this.checkFormConfig,
    }],
  })(
    <RuleConfig alarmConfig={alarmConfig} /> )} </FormItem>
複製代碼
value爲字段的值,cb爲出錯時顯示的校驗信息,可是cb必須調用


5. 自定義組件

關鍵詞:受控模式
  • 提供受控屬性 value 或其它與 valuePropName 的值同名的屬性。
  • 提供 onChange 事件或 trigger 的值同名的事件。
  • 不能是函數式組件。
在表單裏使用自定義組件要遵循上面的約定。
注意: 在對自定義組件使用校驗時,須要注意範圍,由於antd把出錯組件的全部輸入框都會加上validator-error樣式,甚至下拉框都會有提醒


6. 自定義組件的表單域嵌套

若是你的組件中只有一個表單元素,那麼你能夠像上述所說的直接使用,可是組件中包含子組件,就不能在外層使用FormItem了。
...
<FormItem>
</FormItem>
<Parent /> //不用在Parent外面包裝Item,在實際使用input的地方使用

class Parent extend PureComponent {
    render () {
        return <Child /> } } class Child extend PureComponent { render () { <FormItem> // 在實際的輸入上加Item { getFieldDecorator('continuous', { initialValue: continuous, rules: [ { required: true, message: 'please input u continuous', }, { validator: this.validate, }, ], })( // 絕對不用在這裏包裹div,否則就接收不到change <Select onChange={this.onChangeContinuous} > {continuousList} </Select> ) } </FormItem> } } 複製代碼
若是是map出來的子組件,須要肯定getFieldDecorator的id不同,否則全部的id綁定一個值


7. 獲取自定義組件的值

好比自定義組件是這個 <MyComponent />
class MyComponent extends PureComponent {
    constructor(){
        super();
        this.state = {
            receiverList: [],// 受控屬性
        }
    }

    onReceiverChange = receiverList => {
        this.setState({ receiverList });

        // 用於Form的回調
        if (this.props.onChange) {
          this.props.onChange(receiverList);
        }
    }

    render() {
        const receiverList = this.state.receiverList;

        return (
            <Input onChange={this.onReceiverChange} value={receiverList}/> ); } } 複製代碼
Form中這樣寫就好:
...
<Form>
    <Form.Item> {getFieldDecorator('receiverList', { rules: [{ required: true, message: 'Please input name!', }], })( <Receiver receiverList /> )} </Form.Item> </Form> 複製代碼
receiverList就是自定義組件中的受控屬性。如今自定義組件中的受控屬性已經賦值給了form.receiverList,也能夠受Form的rule控制了,是否是很棒~

神坑:若是是表單元素呢,例如select之類的,絕對不要在外面寫div
<Form>
    <Form.Item> {getFieldDecorator('receiverList', { rules: [{ required: true, message: 'Please input name!', }], })( //<div> 絕對不要在這裏加div,不然不能獲取select的change事件!!!!! <Select> <Option value=1>1</Option> </Select> )} </Form.Item> </Form>複製代碼


ToolTip使用指南

1. 點擊事件冒泡

demo:
<Popconfirm
  title={intl.find('alarm.attributes.sureToDelete')}
  trigger="click"
  onClick={this.onDelete(alarmId)}
>
  <Icon type="delete" /> </Popconfirm>
複製代碼
一個很是常見的例子,點擊icon彈出popconfirm。
但是, 當點擊圖表,會發現popconfirm一閃而過,直接執行了onDelete事件,爲何呢?
由於popconfirm提供了onConfirm,在點擊圖標時,click事件往上冒泡,因而在popconfirm中觸發了onClick,知道了過程,修復也很簡單,以下:
<Popconfirm
  title={intl.find('alarm.attributes.sureToDelete')}
  trigger="click"
  onConfirm={this.onDelete(alarmId)} // 使用正確的監聽函數
>
  <Icon type="delete" onClick={evt => { evt.stopPropagation(); // 阻止事件冒泡 }} /> </Popconfirm>
複製代碼


2. popconfirm嵌套問題

若是你的設計稿是存在popconfirm的嵌套關係,長得像下面同樣:
<Popover
  placement="bottomRight"
  content={content}
  trigger="click"
  visible={this.state.visible}
  onVisibleChange={this.handVisibleChange}
  overlayClassName="xri-alarm-config-popover"
>
  <i className={`${alarms.length > 0 && alarms[0].alarmObject.status === 'NORMAL' ? 'alarm-config-icon_useable' : 'alarm-config-icon'} feature-icon`} title={intl.find('charts.wrapHead.alarmConfig.title')} onClick={this.changVisible} /> </Popover>

content:
<Popconfirm title='title' onConfirm={this.onConfirm} onCancel={this.onCancel} okText='ok' cancelText='取消' visible={this.isShowPopConfirm(index)} getPopupContainer={this.getPopupContainer} onVisibleChange={this.handVisibleChange} overlayClassName="xri-alarm-popconfirm-sync" > <Tooltip title={intl.find('alarm.edit')} trigger="hover"> <span className="icon" onClick={this.checkSync(item, index)}> <Icon type="edit"/> </span> </Tooltip> </Popconfirm> 複製代碼
你會發現,當你點擊內層popconfirm時,竟然把外層全部的tooltip類控件都關掉了,真是太神奇了。。。
試着去阻止內層事件的冒泡,這樣上層的組件就不會響應事件了,完美的構思,合情合理,nice
可是,竟然沒用,好吧,上大招,源碼解決一切。
在rc-toolTip的文檔裏有很是重要的一個描述
Function returning html node which will act as tooltip container. By default the tooltip attaches to the body. If you want to change the container, simply return a new element.
底層的 re-trigger這樣寫着:
getContainer = () => {
const { props } = this;
const popupContainer = document.createElement('div');
// Make sure default popup container will never cause scrollbar appearing
// https://github.com/react-component/trigger/issues/41
popupContainer.style.position = 'absolute';
popupContainer.style.top = '0';
popupContainer.style.left = '0';
popupContainer.style.width = '100%';
const mountNode = props.getPopupContainer ?
props.getPopupContainer(findDOMNode(this)) : props.getDocument().body;
mountNode.appendChild(popupContainer);
return popupContainer;
}
也就是說,全部基於tooltip的組件默認是以body做爲父組件的,這也就是爲何阻止了冒泡外層組件仍然會受影響的緣由,當觸發visibleChange時,全部的組件都響應了,因此解決思路:改變父級關係,其實re-tigger提供了更改默認父級的API,antd也將這個API提供給開發,那就是 getPopupContainer,更改父級關係後,你的visible事件就不會影響其餘組件了,可是要注意class, 由於層級發生變化,因此css的結構須要相應的該調整。


upload使用指南

1. 檢測文件

提供了上傳前檢測的機會,能夠作文件數,文件類型,文件大小等檢測。
function beforeUpload(file, fileList) {
  const isJPG = file.type === 'image/jpeg';
  const isLt2M = file.size / 1024 / 1024 < 2;
  const isGteMax = fileList.length > 3;

  if (!isJPG) {
    message.error('You can only upload JPG file!');
  }

  if (!isLt2M) {
    message.error('Image must smaller than 2MB!');
  }

  if(isGteMax) {
    message.error('Image count smaller than 4');
  }

  return isJPG && isLt2M && isGteMax;
}

...
<Upload
    className="avatar-uploader"
    name="avatar"
    showUploadList={false}
    action="//jsonplaceholder.typicode.com/posts/"
    beforeUpload={beforeUpload}
    multiple=true
    onChange={this.handleChange}
>
    <Button>upload</Button>
</Upload>
複製代碼


2. 手動上傳

upload默認是選中了文件直接上傳的,若是想完成一些業務邏輯後手動上傳,能夠利用beforeUpload返回false後,手動上傳文件,使用new FormData();
ps: FormData
import { Upload, Button, Icon, message } from 'antd';
import reqwest from 'reqwest';

class Demo extends React.Component {
  state = {
    fileList: [],
    uploading: false,
  }

  handleUpload = () => {
    const { fileList } = this.state;
    const formData = new FormData();
    fileList.forEach((file) => {
      formData.append('files[]', file);
    });

    this.setState({
      uploading: true,
    });

    // You can use any AJAX library you like
    reqwest({
      url: '//jsonplaceholder.typicode.com/posts/',
      method: 'post',
      processData: false,
      data: formData,
      success: () => {
        this.setState({
          fileList: [],
          uploading: false,
        });
        message.success('upload successfully.');
      },
      error: () => {
        this.setState({
          uploading: false,
        });
        message.error('upload failed.');
      },
    });
  }

  render() {
    const { uploading } = this.state;
    const props = {
      action: '//jsonplaceholder.typicode.com/posts/',
      onRemove: (file) => {
        this.setState(({ fileList }) => {
          const index = fileList.indexOf(file);
          const newFileList = fileList.slice();
          newFileList.splice(index, 1);
          return {
            fileList: newFileList,
          };
        });
      },
      beforeUpload: (file) => {
        this.setState(({ fileList }) => ({
          fileList: [...fileList, file],
        }));
        return false;
      },
      fileList: this.state.fileList,
    };

    return (
      <div> <Upload {...props}> <Button> <Icon type="upload" /> Select File </Button> </Upload> <Button className="upload-demo-start" type="primary" onClick={this.handleUpload} disabled={this.state.fileList.length === 0} loading={uploading} > {uploading ? 'Uploading' : 'Start Upload' } </Button> </div> ); } } ReactDOM.render(<Demo />, mountNode); 複製代碼


3. 自定義上傳行爲

須要自定義長傳行爲時,主要利用customRequest,去實現自定義的默認上傳動做
/* eslint no-console:0 */
import React from 'react';
import ReactDOM from 'react-dom';
import Upload from 'rc-upload';
import axios from 'axios';

const uploadProps = {
  action: '/upload.do',
  multiple: false,
  data: { a: 1, b: 2 },
  headers: {
    Authorization: '$prefix $token',
  },
  onStart(file) {
    console.log('onStart', file, file.name);
  },
  onSuccess(ret, file) {
    console.log('onSuccess', ret, file.name);
  },
  onError(err) {
    console.log('onError', err);
  },
  onProgress({ percent }, file) {
    console.log('onProgress', `${percent}%`, file.name);
  },
  customRequest({
    action,
    data,
    file,
    filename,
    headers,
    onError,
    onProgress,
    onSuccess,
    withCredentials,
  }) {
    // EXAMPLE: post form-data with 'axios'
    const formData = new FormData();
    if (data) {
      Object.keys(data).map(key => {
        formData.append(key, data[key]);
      });
    }
    formData.append(filename, file);

    axios
      .post(action, formData, {
        withCredentials,
        headers,
        onUploadProgress: ({ total, loaded }) => {
          onProgress({ percent: Math.round(loaded / total * 100).toFixed(2) }, file);
        },
      })
      .then(({ data: response }) => {
        onSuccess(response, file);
      })
      .catch(onError);

    return {
      abort() {
        console.log('upload progress is aborted.');
      },
    };
  },
};

const Test = () => {
  return (
    <div style={{ margin: 100, }} > <div> <Upload {...uploadProps}> <button>開始上傳</button> </Upload> </div> </div>
  );
};

ReactDOM.render(<Test />, document.getElementById('__react-content'));複製代碼


結尾

目前總結了 Form,ToolTip, Upload的能力邊界,但願對你們有用~css

相關文章
相關標籤/搜索