React.js -- 优化你的表单

前端之家收集整理的这篇文章主要介绍了React.js -- 优化你的表单前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

React Form

在构建 web 应用的时候,为了采集用户输入,表单变成了我们不可或缺的东西。大型项目中,如果没有对表单进行很好的抽象和封装,随着表单复杂度和数量增加,处理表单将会变成一件令人头疼的事情。在 react 里面处理表单,一开始也并不容易。所以在这篇文章中,我们会介绍一些简单的实践,让你能够在 react 里面更加轻松的使用表单。如果你对 HTML 表单的基础掌握得不是太好,那么我建议你先阅读我的上一篇文章 深入理解 HTML 表单

好了,废话不多说,让我们先来看一个简单的例子。

示例

LoginForm.js

  1. handleChange = evt => {
  2. this.setState({
  3. username: evt.target.value,});
  4. };
  5.  
  6. render() {
  7. return (
  8. <form>
  9. <label>
  10. username:
  11. <input
  12. type="text"
  13. name="username"
  14. value={this.state.username}
  15. onChange={this.handleChange}
  16. />
  17. </label>
  18. <input
  19. type="submit"
  20. value="Submit"
  21. />
  22. </form>
  23. );
  24. }

在上面的例子中,我们创建了一个输入框,期望用户在点击 submit 之后,提交用户输入。

移步这里
查看文章中的全部代码

数据的抽象

对于每一个表单元素来说,除开 DOM 结构的不一样,初始值,错误信息,是否被 touched,是否 valid,这些数据都是必不可少的。所以,我们可以抽象一个中间组件,将这些数据统一管理起来,并且适应不同的表单元素。这样 Field 组件 就应运而生了。

Field 作为一个中间层,包含表单元素的各种抽象。最基本的就是 Field 的名字对应的值
Field 不能单独存在,因为 Field 的 value 都是来自传入组件的 state,传入组件通过 setState 更新 state,使 Field 的 value 发生变化

  1. Field: {
  2. name: String,// filed name,相当于上面提到的 key
  3. value: String,// filed value
  4. }

在实际情况中,还需要更多的数据来控制 Field 的表现行为,比如 valid,invalid,touched 等。

  1. Field:{
  2. name: String,// filed value
  3. label: String,error: String,initialValue: String,valid: Boolean,invalid: Boolean,visited: Boolean,// focused
  4. touched: Boolean,// blurred
  5. active: Boolean,// focusing
  6. dirty: Boolean,// 跟初始值不相同
  7. pristine: Boolean,// 跟初始值相同
  8. component: Component|Function|String,// 表单元素
  9. }

点这里了解 => Redux Form 对 Field 的抽象

UI的抽象

Field 组件

  1. 作为通用抽象,Field对外提供一致接口。 一致的接口能够使 Field 的使用起来更加的简单。比如更新 checkBox 的时候,我们更新的是它的 checked 属性而不是 value 属性,但是我们可以对 Field 进行封装,对外全部提供 value 属性,使开发变得更加容易。

  2. 作为中间层,Field可以起到拦截作用。 如先格式化传入的 value,再将这个 value 传递给下层的组件,这样所有下层组件得到的都是格式化之后的值。

Field.js

  1. static defaultProps = {
  2. component: Input,};
  3. render() {
  4. const { component,noLabel,label,...otherProps } = this.props;
  5. return (
  6. <label>
  7. {!noLabel && <span>{label}</span>}
  8. {
  9. createElement(component,{ ...otherProps })
  10. }
  11. </label>
  12. );
  13. }

上面的例子是 Field 组件的简单实现。Field 对外提供了统一的 label 和 noLabel 接口,用来显示不显示 label 元素。

Input 组件

创建Input 组件的关键点在于使它变得“可控”,也就是说它并不维护内部状态。关于可控组件,接下来会介绍。

Input.js

  1. handleChange = evt => {
  2. this.props.onChange(evt.target.value);
  3. };
  4.  
  5. render() {
  6. return (
  7. <input {...this.props} onChange={this.handleChange} />
  8. );
  9. }

看上面的代码,为什么不直接把 onChange 函数通过 props 传进来呢?就像下面这样

  1. render() {
  2. return (
  3. <input {...this.props} onChange={this.props.onChange} />
  4. );
  5. }

其实是为了让我们从 onChange 回调中得到 统一的 value,这样我们在外部就不用去 care 究竟是 取 event.target.value 还是 event.target.checked.

优化后的 LoginForm 如下:

LoginForm.js

  1. class LoginForm extends Component {
  2. state = {
  3. username: '',};
  4. handleChange = value => {
  5. this.setState({
  6. username: value,});
  7. };
  8. render() {
  9. return (
  10. <form onSubmit={this.handleSubmit}>
  11. <Field
  12. label="username"
  13. name="username"
  14. value={this.state.username}
  15. onChange={this.handleChange}
  16. />
  17. <input
  18. type="submit"
  19. value="Submit"
  20. />
  21. </form>
  22. );
  23. }
  24. }

可控组件与不可控组件

可控组件与不可控组件最大的区别就是:对内部状态的维护与否

一个可控的 <input> 应该具有哪些特点?

  1. 通过 props 提供 value。可控组件并不维护自己的内部状态,也就是外部提供什么,就显示什么,所以组件能够通过 props 很好的控制起来

  2. 通过 onChange 更新value。

  1. <input
  2. type="text"
  3. value={this.props.username}
  4. onChange={this.handleChange}
  5. />

点这里了解 => React 可控组件与不可控组件

使用 React 高阶组件进一步优化

在 LoinForm.js 中可以看到,我们对 setState 操作的依赖程度很高。如果在 form 中多添加一些 Field 组件,不难发现对于每一个 Field,都需要重复 setState 操作。过多的 setState 会我们的Form 组件变得不可控,增加维护成本。

仔细观察上面的代码,不难发现,在每一次 onChange 事件中,都是通过一个 keyvalue更新到 state 里面。比如上面的例子中,我们是通过 username 这个 key 去更新的。所以不难想到,利用高阶组件,可以不用在 LoginForm 里面维护内部状态。

高阶组件在这里就不再展开了,我会在接下来的文章中专门来详细介绍这一部分内容

withState.js

  1. const withState = (stateName,stateUpdateName,initialValue) =>
  2. BaseComponent =>
  3. class extends Component {
  4. state = {
  5. stateValue: initialValue,};
  6.  
  7. updateState = (stateValue) => {
  8. this.setState({
  9. stateValue,});
  10. };
  11.  
  12. render() {
  13. const { stateValue } = this.state;
  14. return createElement(BaseComponent,{
  15. ...this.props,[stateName]: stateValue,[stateUpdateName]: this.updateState,});
  16. }
  17. };

除了 state 之外,我们可以将 onChange,onSubmit 等事件处理函数也 extract 出去,这样可以进一步简化我们的 Form。

withHandlers.js

  1. const withHandlers = handlers => BaseComponent =>
  2. class WithHandler extends Component {
  3. cachedHandlers = {};
  4.  
  5. handlers = mapValues(
  6. handlers,(createHandler,handlerName) => (...args) => {
  7. const cachedHandler = this.cachedHandlers[handlerName];
  8. if (cachedHandler) {
  9. return cachedHandler(...args);
  10. }
  11.  
  12. const handler = createHandler(this.props);
  13. this.cachedHandlers[handlerName] = handler;
  14. return handler(...args);
  15. }
  16. );
  17.  
  18. componentWillReceiveProps() {
  19. this.cachedHandlers = {};
  20. }
  21.  
  22. render() {
  23. return createElement(BaseComponent,{
  24. ...this.props,...this.handlers,});
  25. }
  26. };

使用高阶组件改造后的 LoginForm 如下:

LoginForm.js

  1. const withLoginForm = _.flowRight(
  2. withState('username','onChange',''),withHandlers({
  3. onChange: props => value => {
  4. props.onChange(value);
  5. },onSubmit: props => event => {
  6. event.preventDefault();
  7. console.log(props.username);
  8. },})
  9. );
  10.  
  11. @withLoginForm
  12. class LoginForm extends Component {
  13. static propTypes = {
  14. username: PropTypes.string,onChange: PropTypes.func,onSubmit: PropTypes.func,};
  15.  
  16. render() {
  17. const { username,onChange,onSubmit } = this.props;
  18. return (
  19. <form onSubmit={onSubmit}>
  20. <Field
  21. label="username"
  22. name="username"
  23. value={username}
  24. onChange={onChange}
  25. />
  26. <input
  27. type="submit"
  28. value="Submit"
  29. />
  30. </form>
  31. );
  32. }
  33. }

通过 composewithStatewithHandler 组合起来,并应用到 Form 之后,跟之前比起来,LoginForm 已经简化了很多。LoginForm 不再自己维护内部状态,变成了一个完完全全的可控组件,不管是之后要对它写测试还是要重用它,都变得十分的轻松了。

点这里了解 => Recompose

结语

对于复杂的项目来说,以上的抽象还远远不够,在下一篇文章中,会介绍如何进一步让你的 Form 变得更好用。

猜你在找的React相关文章