前言
本文用於記錄在使用andt相關元件時的採坑合集,挖掘一下元件的能力邊界
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直接繫結會清爽很多。
注意:
- 一個 Form.Item 建議只放一個被 getFieldDecorator 裝飾過的 child,當有多個被裝飾過的 child 時,
help
required
validateStatus
無法自動生成。 2.2.0
之前,只有當表單域為 Form.Item 的子元素時,才會自動生成help
required
validateStatus
,巢狀情況需要自行設定。- 不能再用value/defaultValue來設定表單的值,只能用initialValue設定。
- 不能裝飾純元件,畢竟是修飾器。。。
2. 將表單資料與上層元件互動
關鍵詞:mapPropsToFields, onFieldsChange, onValuesChange
mapPropsToFields: 把父元件的屬性對映到表單項上(可用於把 Redux store 中的值讀出)
onFieldsChange: 當
Form.Item
子節點的值發生改變時觸發,可以把對應的值轉存到 Redux storedemo:
@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的能力邊界,希望對大家有用~