rc-form-demo:
import React from 'react';
import ReactDOM from 'react-dom';
import { createForm, formShape } from 'rc-form';
class Form extends React.Component {
static propTypes = {
form: formShape,
};
componentWillMount() {
this.nameDecorator = this.props.form.getFieldDecorator('name', {
initialValue: '',
rules: [{
required: true,
message: 'What\'s your name?',
}],
});
}
onSubmit = (e) => {
e.preventDefault();
this.props.form.validateFields((error, values) => {
if (!error) {
console.log('ok', values);
} else {
console.log('error', error, values);
}
});
};
onChange = (e) => {
console.log(e.target.value);
}
render() {
const { getFieldError } = this.props.form;
return (
<form onSubmit={this.onSubmit}>
{this.nameDecorator(
<input
onChange={this.onChange}
/>
)}
<div style={{ color: 'red' }}>
{(getFieldError('name') || []).join(', ')}
</div>
<button>Submit</button>
</form>
);
}
}
const WrappedForm = createForm()(Form);
ReactDOM.render(<WrappedForm />, document.getElementById('__react-content'));
複製程式碼
上面的例子直接呼叫了rc-form的createForm方法,第一個引數配置物件未傳遞,第二個引數是需要被修飾的業務元件 在createForm方法中只是定義了mixin,接著繼續呼叫了最主要的createBaseForm:
function createBaseForm(options={}, mixins={} ) {
const {
mapPropsToFields, // 頁面初始化或重繪時,將元件接受到的props轉變為表單項資料注入this.fields中
onFieldsChange, // 表單項發生改變時執行函式,可以將表單項的值存入redux.store
...
withRef, // 設定被封裝元件的ref屬性為"wrappedComponent"
} = option;
//這裡的WrappenComponent就是一開始例子中的Form元件
return funciton decorate(WrappedComponent) {
const Form = createReactClass({
mixins,
getInitialState(){}, // 初始化元件state
componentWillReceiveProps(nextProps){}, // mark-recProps,初始化部分資料
onCollect(){}, // 收集表單資料
onCollectCommon() {},
getCacheBind() {}, // 元件事件繫結等收集
getFieldDecorator() {}, // 裝飾元件,促進雙向繫結的修飾器
getFieldProps() {} // 設定欄位後設資料,計算被修飾元件的屬性
...
render() {
const { wrappedComponentRef, ...restProps } = this.props;
const formProps = {
[formPropName]: this.getForm(), // createForm mixin
};
// 其中mapProps函式就是一個function(obj) {return obj};
// 這裡用了一個小技巧,就是call(this,xxx),直接將該元件上的核心方法,全都放到了子元件的屬性上,而且由於該元件是createReactClass建立的,所以子元件(本例中的Form)呼叫這些從父元件獲取的方法時,方法內部的this,指向當前元件。
const props = mapProps.call(this, {
...formProps,
...restProps,
});
// 把form屬性掛在到WrappedComponent屬性上
return <WrappedComponent {...props}/>;
},
},
})
// 複製包裹元件的靜態屬性到Form上
return argumentContainer(Form, WrappedComponent);
}
}
複製程式碼
- 產生一個新容器元件Form,內建getFieldDecorator、getFieldProps等屬性和方法
- 複製被包裹元件的靜態屬性到新的組建中,執行生命週期時間,getInitialState初始化預設的field,預設無
- 最後返回被注入了Form元件的原始元件
getFieldDecorator
getFieldDecorator(name, fieldOption) {
// 在getFieldProps中初始化store欄位,繫結onChange事件以便後續方便做雙向資料繫結
const props = this.getFieldProps(name, fieldOption);
//props: {value: "", ref: ƒ, onChange: ƒ}
return (fieldElem) => { // 此處傳入被修飾的input元素
// fieldStore儲存欄位資料資訊以及後設資料資訊。
// 資料資訊包括value,errors,dirty等
// 後設資料資訊包括initValue,defaultValue,校驗規則等。
const fieldMeta = this.fieldsStore.getFieldMeta(name);
// 獲取input上本身繫結的屬性,例如該例子中的onChange列印內容函式
// originalProps屬性的主要目的儲存被封裝表單項的onChange事件,fieldOption下無同類事件時,執行該事件
const originalProps = fieldElem.props;
fieldMeta.originalProps = originalProps;
fieldMeta.ref = fieldElem.ref;
// clone input元件,並注入新的屬性,onchange,value等
return React.cloneElement(fieldElem, {
...props,
...this.fieldsStore.getFieldValuePropValue(fieldMeta),
});
};
getFieldProps(name, usersFieldOption = {}) {
const fieldOption = {
name, // 自定義元件名稱
trigger: DEFAULT_TRIGGER, // 繫結預設的onChange事件
valuePropName: 'value', // 預設值是value屬性,checkBox的值為checked
validate: [],
...usersFieldOption,
};
const {
rules,
trigger,
validateTrigger = trigger,
validate,
} = fieldOption;
const fieldMeta = this.fieldsStore.getFieldMeta(name);
const inputProps = {
...this.fieldsStore.getFieldValuePropValue(fieldOption),
ref: this.getCacheBind(name, `${name}__ref`, this.saveRef),
};
//轉換成陣列格式
const validateRules = normalizeValidateRules(validate, rules, validateTrigger);
//validateRules [{"trigger":["onChange"],"rules":[{"required":true,"message":"What's your name?"}]}]
const validateTriggers = getValidateTriggers(validateRules);
validateTriggers.forEach((action) => {
if (inputProps[action]) return;
//繫結事件
inputProps[action] = this.getCacheBind(name, action, this.onCollectValidate);
});
//FieldMeta值
const meta = {
...fieldMeta,
...fieldOption,
validate: validateRules,
};
// 設定FieldMeta
this.fieldsStore.setFieldMeta(name, meta);
return inputProps;
}
複製程式碼
將表單項包裝為高階元件
- 建立表單資訊到fieldsStore
- 繫結預設的onChange事件
- 觸發驗證
- 儲存結果到fieldsStore
- 返回雙向資料繫結的input元件
在上述getFieldProps中onChange繫結了onCollect方法, 當change事件發生時,獲取表單項的改變值,有校驗規則的表單項新增{dirty:true}屬性,呼叫validateFieldsInternal方法校驗該表單項,將結果儲存到fieldsStore,觸發forceUpdate來重繪表單
onCollect(name_, action, ...args) {
const { name, field, fieldMeta } = this.onCollectCommon(name_, action, args);
const { validate } = fieldMeta;
const newField = {
...field,
dirty: hasRules(validate),
};
this.setFields({
[name]: newField,
});
},
複製程式碼
Form內部有自己的狀態管理:fieldsStore記錄著所有表單項的資訊,通過getFieldDecorator和表單進行雙向繫結。
onChange觸發onCollect來改變fieldStore中的值並觸發forceUpdate來更新,onCollectCommon方法內部展示了onCollect取值的細節,forceUpdate在更新元件後,觸發render方法,接著又回到一開始getFieldDecorator中獲取fieldStore內的值,返回被修改後的元件的流程。