在构建 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 变得更好用。