技术控

    今日:159| 主题:57868
收藏本版 (1)
最新软件应用技术尽在掌握

[其他] Elegant Form Validation Using React

[复制链接]
下一秒落寞 投递于 2016-10-6 10:28:17
179 2
Over the past few months, I’ve really been enjoying learning to use    Reactfor front-end web development. I love how React encourages you to write clean code by breaking your presentation components into small chunks that are easy to reuse.  
  Lately, I’ve been working on an approach to add form validation to React components, and I’d like to share what I came up with.  
  A Sample Problem

  My goal was to hide all errors initially, waiting until the user presses “submit” to present any errors. Once the errors were visible, I wanted them to go live and automatically clear as the user changes the input data.
      

Elegant Form Validation Using React

Elegant Form Validation Using React-1-技术控-definition,breaking,learning,waiting,example
  
  Declaring validation rules

  In this example, I’m adding validation to the text fields on a Create Account screen. Looking at the CreateAccount.jsx component below, the first thing you’ll see is the definition of    fieldValidations. This is where I define the set of validations that should be performed on each field in the form.  
  I did this by creating a list of    ruleRunnerswhich involves passing a key, a name, and a list of rules. The key is used to identify which value in the component’s state the rule should be run on. The name is a friendly name that will be used to construct an error message when validation fails.  
      The following line sets up validation for the password field.
      
  1. ruleRunner("password1", "Password", required, minLength(6)),
复制代码
This means that    this.state.password1is required to have a value, and that value's minimum length is six characters. If validation fails, the field should be referred to as "Password" in the resulting error message.  
  handleFieldChanged()

  The next important piece of the CreateAccount component is the    handleFieldChanged()function. This is the function that is to be called whenever the text in any of the form fields changes. It starts by creating a copy of this.state and updating it with the value that just changed. It then calls run(), passing in the new state and the list of validation ruleRunners that we declared above.  
  The run() function performs the validation checks and returns a key-value pair or validation errors. If the value that changed was the password, and it was only five characters, the result would look something like this:
  1. {"password1": "Password must be at least 6 characters"}.
复制代码
The result of run() is assigned to newState.validationErrors.
  Set the component state

  Finally, we call    this.setState(newState)to commit the change and the updated validation results in our component state.  
  When the user submits the form data, the validation is already complete. All we have to do is check if    validationErrorsis an empty object or not. If it's empty, we can go ahead and perform whatever we need to do to submit the data.  
  Making errors live

  One other thing I'd like to point out is the showErrors flag that exists in the component state. This flag is passed to each of the fields that are rendered as an indicator of whether they should show their corresponding error or not. Initially, all the fields will likely have errors on them because they are empty.
  However, since the user hasn't attempted to submit yet, we don't want to show these errors. The    showErrorsflag is defaulted to false, but set to true once the the user clicks submit. From that point on, all errors are visible and updated live as the user changes the form data.  
  1. // CreateAccount.jsx

  2. const fieldValidations = [
  3.   ruleRunner("name", "Name", required),
  4.   ruleRunner("emailAddress", "Email Address", required),
  5.   ruleRunner("password1", "Password", required, minLength(6)),
  6.   ruleRunner("password2", "Password Confirmation", mustMatch("password1", "Password"))
  7. ];

  8. export default class CreateAccount extends React.Component {
  9.   constructor() {
  10.     // ...
  11.     this.state = {
  12.       showErrors: false,
  13.       validationErrors: {}
  14.     };

  15.     // Run validations on initial state
  16.     this.state.validationErrors = run(this.state, fieldValidations);
  17.   }
  18.   
  19.   handleFieldChanged(field) {
  20.     return (e) => {
  21.       // update() is provided by React Immutability Helpers
  22.       // https://facebook.github.io/react/docs/update.html
  23.       let newState = update(this.state, {
  24.         [field]: {$set: e.target.value}
  25.       });
  26.       newState.validationErrors = run(newState, fieldValidations);
  27.       this.setState(newState);
  28.     };
  29.    
  30.   handleSubmitClicked() {
  31.     this.setState({showErrors: true});
  32.     if($.isEmptyObject(this.state.validationErrors) == false) return null;
  33.     // ... continue submitting data to server
  34.   }

  35.   render() {
  36.     return (
  37.       <div>
  38.         <TextView placeholder="Email address" showError={this.state.showErrors}
  39.                   text={this.props.emailAddress} onFieldChanged={this.handleFieldChanged("emailAddress")}
  40.                   errorText={this.errorFor("emailAddress")} />
  41.         
  42.         // Render Name, Password, Submit button, etc. fields
  43.       <div>
  44.     );
  45.   }
  46. }
复制代码
ruleRunner

  Next, let's look at the ruleRunner function. ruleRunner is a thunk, or a function that returns a function. We call it to construct a runner function that takes the updated state and runs the specified validations against said state.
  We haven't looked at the implementation of the validation rules yet. For now, you just need to know that they are also thunks. They must return either null (if there was no error) or a function that will be used to construct an error message. That function takes as a parameter the friendly name of the field that was passed in to the original call to ruleRunner() so that a meaningful error message can be made. If a validation check returns a non-null value, the ruleRunner calls the resulting function and then returns a key-value pair of the field key and the error message.
  My implementation only allows one error per field to be reported. It would be very simple to tweak the implementation to return an array of error messages instead to support displaying multiple validation errors per field.
  run()

  The other function that I have defined next to    ruleRunner()is    run(). This is a very simple function that just calls all the validation rule runners and aggregates their results into a single object. It allows us to have the results of all the fields in one object.  
  
  1. // ruleRunner.js

  2. export const ruleRunner = (field, name, ...validations) => {
  3.   return (state) => {
  4.     for (let v  of validations) {
  5.       let errorMessageFunc = v(state[field], state);
  6.       if (errorMessageFunc) {
  7.         return {[field]: errorMessageFunc(name)};
  8.       }
  9.     }
  10.     return null;
  11.   };
  12. };

  13. export const run = (state, runners) => {
  14.   return runners.reduce((memo, runner) => {
  15.     return Object.assign(memo, runner(state));
  16.   }, {});
  17. };
复制代码
Implementing rules

  Moving one level deeper, the next thing to look at is the implementation of the rules. The rules come in two forms. The simplest is just a function that looks at the value being passed in and returns null or an error message constructor. This is how the    requiredrule works.  
  The second form is a bit more complicated because it actually constructs a new function that does the validation check. This allows the rules to be reused on various fields with slightly different requirements. For example, the minLength function constructs a rule that checks for a specific length. It could be used somewhere else to check for a different length.
  You can see the two different forms of rules when looking at the declaration of field validations that we discussed earlier. Notice that the required rule function is not called in the declaration, but only used as a reference to that function. In contrast, the minLength function is called with a value of six during the declaration. If you find that confusing, you could use a naming convention to help distinguish between the two types. For example, you might name them: requiredRule and minLengthRuleBuilder.
  
  1. // rules.js
  2. import * as ErrorMessages from './errorMessages.js';

  3. export const required = (text) => {
  4.   if (text) {
  5.     return null;
  6.   } else {
  7.     return ErrorMessages.isRequired;
  8.   }
  9. };

  10. export const mustMatch = (field, fieldName) => {
  11.   return (text, state) => {
  12.     return state[field] == text ? null : ErrorMessages.mustMatch(fieldName);
  13.   };
  14. };

  15. export const minLength = (length) => {
  16.   return (text) => {
  17.     console.log("Checking for length greater than ", length);
  18.     return text.length >= length ? null : ErrorMessages.minLength(length);
  19.   };
  20. };
复制代码
Building error messages

  When a rule determines that a value is invalid, it returns a function that is used to build an appropriate error message. I like to have all my error messages in one place that is easy to find, so I separate them into an errorMessages file. This also makes them easy to reuse if necessary. Alternatively, you could put the implementation of the error message builders right in the rules.
  
  1. // errorMessages.js

  2. export const isRequired = fieldName => `${fieldName} is required`;

  3. export const mustMatch = otherFieldName => {
  4.   return (fieldName) => `${fieldName} must match ${otherFieldName}`;
  5. };

  6. export const minLength = length => {
  7.   return (fieldName) => `${fieldName} must be at least ${length} characters`;
  8. };
复制代码
That's it for the construction and processing of the validation rules.
  TextField

  The last thing I want to show is my TextField component. As you can see, this component is completely stateless. It displays the value that was passed in. When the user changes the value, it calls the onFieldChanged callback that was also passed in as props. For the error message functionality, it looks at two other props,    showErrorand    errorText. If    showErroris true and errorMessage contains a value, the error message is rendered.  
  
  1. // TextField.jsx

  2. import React from 'react';
  3. import OptionallyDisplayed from './OptionallyDisplayed.jsx';

  4. export default class TextField extends React.Component {

  5.   constructor(props) {
  6.     super(props);
  7.     this.shouldDisplayError = this.shouldDisplayError.bind(this);
  8.   }

  9.   shouldDisplayError() {
  10.     return this.props.showError && this.props.errorText != "";
  11.   }

  12.   render() {
  13.     return (
  14.       <div>
  15.         <input type="text" placeholder={this.props.placeholder}
  16.                value={this.props.text} onChange={this.props.onFieldChanged}  />
  17.         <OptionallyDisplayed display={this.shouldDisplayError()}>
  18.           <div className="validation-error">
  19.             <span className="text">{this.props.errorText}</span>
  20.           </div>
  21.         </OptionallyDisplayed>
  22.       </div>
  23.     );
  24.   }
  25. }

  26. TextField.propTypes = {
  27.   showError: React.PropTypes.bool.isRequired,
  28.   onFieldChanged: React.PropTypes.func.isRequired
  29. };
复制代码
So that is my approach to form validation of React components. I like it because adding validation to a new form requires only a few lines of code, and the rules are specified in a very declarative way. I also like how the implementation is split up into small manageable pieces, each serving a very specific purpose.
  I want to give credit to my coworker Chris Farber, who worked with me to come up with the original implementation of this approach.



上一篇:Building Your Own React Clone in Five Easy Steps
下一篇:The Rise and Fall of Scala
白萱 投递于 2016-10-12 15:20:09
2016-10-12是什么日子?
回复 支持 反对

使用道具 举报

huihaitong 投递于 2016-11-14 09:20:38
顶贴不认真,大脑有问题。
回复 支持 反对

使用道具 举报

我要投稿

回页顶回复上一篇下一篇回列表
手机版/CoLaBug.com ( 粤ICP备05003221号 | 文网文[2010]257号 | 粤公网安备 44010402000842号 )

© 2001-2017 Comsenz Inc.

返回顶部 返回列表