在構建 web 應用的時候,爲了採集用戶輸入,表單變成了咱們不可或缺的東西。大型項目中,若是沒有對錶單進行很好的抽象和封裝,隨着表單複雜度和數量的增長,處理表單將會變成一件使人頭疼的事情。在 react 裏面處理表單,一開始也並不容易。因此在這篇文章中,咱們會介紹一些簡單的實踐,讓你可以在 react 裏面更加輕鬆的使用表單。若是你對 HTML 表單的基礎掌握得不是太好,那麼我建議你先閱讀個人上一篇文章 深刻理解 HTML 表單html
好了,廢話很少說,讓咱們先來看一個簡單的例子。react
LoginForm.jsgit
handleChange = evt => { this.setState({ username: evt.target.value, }); }; render() { return ( <form> <label> username: <input type="text" name="username" value={this.state.username} onChange={this.handleChange} /> </label> <input type="submit" value="Submit" /> </form> ); }
在上面的例子中,咱們建立了一個輸入框,指望用戶在點擊 submit 以後,提交用戶輸入。github
移步 這裏
查看文章中的所有代碼
對於每個表單元素來講, 除開 DOM 結構的不同,初始值, 錯誤信息, 是否被 touched, 是否 valid,這些數據都是必不可少的。因此,咱們能夠抽象一箇中間組件,將這些數據統一管理起來,而且適應不一樣的表單元素。這樣 Field 組件 就應運而生了。web
Field 做爲一箇中間層,包含表單元素的各類抽象。最基本的就是 Field 的名字 和 對應的值。
Field 不能單獨存在,由於 Field 的 value 都是來自傳入組件的 state, 傳入組件經過 setState 更新 state, 使 Field 的 value 發生變化redux
Field: { name: String, // filed name, 至關於上面提到的 key value: String, // filed value }
在實際狀況中, 還須要更多的數據來控制 Field 的表現行爲,好比 valid
, invalid
, touched
等。segmentfault
Field:{ name: String, // filed name, 至關於上面提到的 key value: String, // filed value label: String, error: String, initialValue: String, valid: Boolean, invalid: Boolean, visited: Boolean, // focused touched: Boolean, // blurred active: Boolean, // focusing dirty: Boolean, // 跟初始值不相同 pristine: Boolean, // 跟初始值相同 component: Component|Function|String, // 表單元素 }
點這裏瞭解 => Redux Form 對 Field 的抽象api
checked
屬性而不是 value
屬性,可是咱們能夠對 Field 進行封裝,對外所有提供 value 屬性,使開發變得更加容易。Field.js函數
static defaultProps = { component: Input, }; render() { const { component, noLabel, label, ...otherProps } = this.props; return ( <label> {!noLabel && <span>{label}</span>} { createElement(component, { ...otherProps }) } </label> ); }
上面的例子是 Field 組件的簡單實現。Field 對外提供了統一的 label 和 noLabel 接口,用來顯示或不顯示 label 元素。
建立Input 組件的關鍵點在於使它變得「可控」,也就是說它並不維護內部狀態。關於可控組件,接下來會介紹。測試
Input.js
handleChange = evt => { this.props.onChange(evt.target.value); }; render() { return ( <input {...this.props} onChange={this.handleChange} /> ); }
看上面的代碼,爲何不直接把 onChange 函數經過 props 傳進來呢?就像下面這樣
render() { return ( <input {...this.props} onChange={this.props.onChange} /> ); }
實際上是爲了讓咱們從 onChange 回調中獲得 統一的value
, 這樣咱們在外部就不用去 care 到底是 取event.target.value
仍是event.target.checked
.
優化後的 LoginForm 以下:
LoginForm.js
class LoginForm extends Component { state = { username: '', }; handleChange = value => { this.setState({ username: value, }); }; render() { return ( <form onSubmit={this.handleSubmit}> <Field label="username" name="username" value={this.state.username} onChange={this.handleChange} /> <input type="submit" value="Submit" /> </form> ); } }
可控組件與不可控組件最大的區別就是:對內部狀態的維護與否。
一個可控的 <input> 應該具備哪些特色?
props
提供 value。可控組件並不維護本身的內部狀態,也就是外部提供什麼,就顯示什麼,因此組件可以經過 props 很好的控制起來onChange
更新value。<input type="text" value={this.props.username} onChange={this.handleChange} />
在 LoinForm.js 中能夠看到,咱們對 setState
操做的依賴程度很高。若是在 form 中多添加一些 Field 組件,不難發現對於每個 Field,都須要重複 setState 操做。過多的 setState 會咱們的Form 組件變得不可控,增長維護成本。
仔細觀察上面的代碼,不難發現,在每一次 onChange 事件中,都是經過一個 key
把 value
更新到 state
裏面。好比上面的例子中,咱們是經過 username
這個 key
去更新的。因此不難想到,利用高階組件,能夠不用在 LoginForm 裏面維護內部狀態。
高階組件在這裏就再也不展開了,我會在接下來的文章中專門來詳細介紹這一部份內容。
withState.js
const withState = (stateName, stateUpdateName, initialValue) => BaseComponent => class extends Component { state = { stateValue: initialValue, }; updateState = (stateValue) => { this.setState({ stateValue, }); }; render() { const { stateValue } = this.state; return createElement(BaseComponent, { ...this.props, [stateName]: stateValue, [stateUpdateName]: this.updateState, }); } };
除了 state 以外,咱們能夠將onChange
,onSubmit
等事件處理函數也 extract 出去,這樣能夠進一步簡化咱們的 Form。
withHandlers.js
const withHandlers = handlers => BaseComponent => class WithHandler extends Component { cachedHandlers = {}; handlers = mapValues( handlers, (createHandler, handlerName) => (...args) => { const cachedHandler = this.cachedHandlers[handlerName]; if (cachedHandler) { return cachedHandler(...args); } const handler = createHandler(this.props); this.cachedHandlers[handlerName] = handler; return handler(...args); } ); componentWillReceiveProps() { this.cachedHandlers = {}; } render() { return createElement(BaseComponent, { ...this.props, ...this.handlers, }); } };
使用高階組件改造後的 LoginForm 以下:
LoginForm.js
const withLoginForm = _.flowRight( withState('username', 'onChange', ''), withHandlers({ onChange: props => value => { props.onChange(value); }, onSubmit: props => event => { event.preventDefault(); console.log(props.username); }, }) ); @withLoginForm class LoginForm extends Component { static propTypes = { username: PropTypes.string, onChange: PropTypes.func, onSubmit: PropTypes.func, }; render() { const { username, onChange, onSubmit } = this.props; return ( <form onSubmit={onSubmit}> <Field label="username" name="username" value={username} onChange={onChange} /> <input type="submit" value="Submit" /> </form> ); } }
經過compose
把withState
和withHandler
組合起來, 並應用到 Form 以後,跟以前比起來,LoginForm 已經簡化了不少。LoginForm 再也不本身維護內部狀態,變成了一個完徹底全的可控組件,不論是以後要對它寫測試仍是要重用它,都變得十分的輕鬆了。
對於複雜的項目來講,以上的抽象還遠遠不夠,在下一篇文章中,會介紹如何進一步讓你的 Form 變得更好用。