如何使用 React.JS 正确验证输入值?

Posted

技术标签:

【中文标题】如何使用 React.JS 正确验证输入值?【英文标题】:How to properly validate input values with React.JS? 【发布时间】:2014-07-24 00:58:26 【问题描述】:

我有一个简单的表格。所有组件和状态都保存在 Page 组件中。有 2 个显示标题和 3 个输入字段。第一个输入应该是文本,第二个和第三个应该是整数。当用户输入错误类型的数据时,我希望在输入字段旁边弹出一条错误消息。我的问题与 React.JS 中的最佳实践有关

谁决定该值是否有效?我想输入字段的唯一工作就是将值引导回持有状态的组件,那么这是否意味着只有 Page 才能确定值是否有效?

然后我应该如何让弹出窗口出现? Page 是否必须触发一个新的布尔状态元素,该元素将通过 perp 传递,告诉 Adaptive_Input 显示错误消息?

JSFiddle

JS:

/**
 * @jsx React.DOM
 */
var Adaptive_Input = React.createClass( 
    handle_change: function()
        var new_text = this.refs.input.getDOMNode().value;
        this.props.on_Input_Change(new_text);
    ,
    render: function()
        return (
                <div className='adaptive_placeholder_input_container'>
                    <input 
                        className="adaptive_input"
                        type="text" 
                        required="required" 
                        onChange= this.handle_change
                        ref="input"
                    ></input>
                    <label
                        className="adaptive_placeholder"
                        alt=this.props.initial
                        placeholder=this.props.focused
                    ></label>
                </div>              
                );
    
);

var Form = React.createClass(
    render: function()
        return (
                <form>
                    <Adaptive_Input
                        initial='Name Input'
                        focused='Name Input'
                        on_Input_Change=this.props.handle_text_input
                    />
                    <Adaptive_Input
                        initial='Value 1'
                        focused='Value 1'
                        on_Input_Change=this.props.handle_value_1_input
                    />
                    <Adaptive_Input
                        initial='Value 2'
                        focused='Value 2'
                        on_Input_Change=this.props.handle_value_2_input
                    />
                </form>
                );
    
);

var Page = React.createClass(
    getInitialState: function()
        return 
            Name : "No Name",
            Value_1 : '0',
            Value_2 : '0',
            Display_Value: '0'
        ;
    ,
    handle_text_input: function(new_text)
        this.setState(
                Name: new_text
            );
    ,
    handle_value_1_input: function(new_value)
        console.log("===");
        var updated_display = parseInt(new_value) + parseInt(this.state.Value_2);
        updated_display = updated_display.toString();
        this.setState(
                Display_Value: updated_display 
            );
    ,
    handle_value_2_input: function(new_value)
        var updated_display = parseInt(this.state.Value_1) + parseInt(new_value);
        updated_display = updated_display.toString();
        this.setState(
                Display_Value: updated_display
            );
    ,
    render: function()
        return(
                <div>
                    <h2>this.state.Name</h2>
                    <h2>Value 1 + Value 2 = this.state.Display_Value</h2>
                    <Form
                        handle_text_input=this.handle_text_input
                        handle_value_1_input = this.handle_value_1_input
                        handle_value_2_input = this.handle_value_2_input
                    />
                </div>
        );
    
);

React.renderComponent(<Page />, document.body);

【问题讨论】:

这是一本好书? christianalfoni.github.io/javascript/2014/10/22/… 如果使用 Node,一个简单的解决方案是使用来自 npm 的react-validation。 【参考方案1】:

有时您的应用程序中可以有多个具有类似验证的字段。在这种情况下,我建议您创建公共组件字段来保存此验证。

例如,假设您在应用程序的几个位置强制输入文本。您可以创建一个 TextInput 组件:

constructor(props) 
    super(props); 
    this.state = 
        touched: false, error: '', class: '', value: ''
    


onValueChanged = (event) => 
    let [error, validClass, value] = ["", "", event.target.value];

    [error, validClass] = (!value && this.props.required) ? 
        ["Value cannot be empty", "is-invalid"] : ["", "is-valid"]

    this.props.onChange(value: value, error: error);

    this.setState(
        touched: true,
        error: error,
        class: validClass,
        value: value
    )


render() 
    return (
        <div>
            <input type="text"
                value=this.props.value
                onChange=this.onValueChanged
                className="form-control " + this.state.class
                id="this.props.id"
                placeholder=this.props.placeholder />
            this.state.error ?
                <div className="invalid-feedback">
                    this.state.error
                </div> : null
            
        </div>
    )

然后您就可以在应用程序的任何地方使用这样的组件:

constructor(props) 
    super(props);
    this.state = 
        user: firstName: '', lastName: '',
        formState: 
            firstName:  error: '' ,
            lastName:  error: '' 
        
    


onFirstNameChange = (model) => 
    let user = this.state.user;
    user.firstName = model.value;

    this.setState(
        user: user,
        formState: ...this.state.formState, firstName:  error: model.error 
    )


onLastNameChange = (model) => 
    let user = this.state.user;
    user.lastName = model.value;

    this.setState(
        user: user,
        formState: ...this.state.formState, lastName:  error: model.error 
    )



onSubmit = (e) => 
   // submit logic



render() 
    return (
        <form onSubmit=this.onSubmit>
            <TextInput id="input_firstName"
                value=this.state.user.firstName
                onChange=this.onFirstNameChange
                required = true
                placeholder="First name" />

            <TextInput id="input_lastName"
                value=this.state.user.lastName
                onChange=this.onLastNameChange
                required = true
                placeholder="Last name" />

            this.state.formState.firstName.error || this.state.formState.lastName.error ?
                <button type="submit" disabled className="btn btn-primary margin-left disabled">Save</button>
                : <button type="submit" className="btn btn-primary margin-left">Save</button>
            

        </form>
    )

好处:

您无需重复验证逻辑 表单中的代码更少 - 更具可读性 其他常用的输入逻辑可以保留在组件中 您遵循 React 规则,即组件应尽可能愚蠢

参考。 https://webfellas.tech/#/article/5

【讨论】:

【参考方案2】:

我过去使用过 redux-form 和 formik,最近 React 引入了 Hook,我已经为它构建了一个自定义的 hook。请检查一下,看看它是否使您的表单验证更容易。

Github:https://github.com/bluebill1049/react-hook-form

网址:http://react-hook-form.now.sh

使用这种方法,您也不再进行受控输入。

下面的例子:

import React from 'react'
import useForm from 'react-hook-form'

function App() 
  const  register, handleSubmit, errors  = useForm() // initialise the hook
  const onSubmit = (data) =>  console.log(data)  // callback when validation pass

  return (
    <form onSubmit=handleSubmit(onSubmit)>
      <input name="firstname" ref=register /> /* register an input */

      <input name="lastname" ref=register( required: true ) /> /* apply required validation */
      errors.lastname && 'Last name is required.' /* error message */

      <input name="age" ref=register( pattern: /\d+/ ) /> /* apply a Refex validation */
      errors.age && 'Please enter number for age.' /* error message */

      <input type="submit" />
    </form>
  )

【讨论】:

【参考方案3】:

在输入文本字段及其下方使用 onChange=this.handleChange.bind(this, "name") 方法和 value=this.state.fields["name"] 创建 span 元素以显示错误,请参见下面的示例。

export default class Form extends Component 

  constructor()
    super()
    this.state =
       fields: 
         name:'',
         email: '',
         message: ''
       ,
       errors: ,
       disabled : false
    
  

  handleValidation()
       let fields = this.state.fields;
       let errors = ;
       let formIsValid = true;

       if(!fields["name"])
          formIsValid = false;
          errors["name"] = "Name field cannot be empty";
       

       if(typeof fields["name"] !== "undefined" && !fields["name"] === false)
          if(!fields["name"].match(/^[a-zA-Z]+$/))
             formIsValid = false;
             errors["name"] = "Only letters";
          
       

       if(!fields["email"])
          formIsValid = false;
          errors["email"] = "Email field cannot be empty";
       

       if(typeof fields["email"] !== "undefined" && !fields["email"] === false)
          let lastAtPos = fields["email"].lastIndexOf('@');
          let lastDotPos = fields["email"].lastIndexOf('.');

          if (!(lastAtPos < lastDotPos && lastAtPos > 0 && fields["email"].indexOf('@@') === -1 && lastDotPos > 2 && (fields["email"].length - lastDotPos) > 2)) 
             formIsValid = false;
             errors["email"] = "Email is not valid";
           
      

      if(!fields["message"])
         formIsValid = false;
         errors["message"] = " Message field cannot be empty";
      

      this.setState(errors: errors);
      return formIsValid;
  

  handleChange(field, e)
      let fields = this.state.fields;
      fields[field] = e.target.value;
      this.setState(fields);
  

  handleSubmit(e)
      e.preventDefault();
      if(this.handleValidation())
          console.log('validation successful')
        else
          console.log('validation failed')
        
  

  render()
    return (
      <form onSubmit=this.handleSubmit.bind(this) method="POST">
          <div className="row">
            <div className="col-25">
                <label htmlFor="name">Name</label>
            </div>
            <div className="col-75">
                <input type="text" placeholder="Enter Name"  refs="name" onChange=this.handleChange.bind(this, "name") value=this.state.fields["name"]/>
                <span style=color: "red">this.state.errors["name"]</span>
            </div>
          </div>
          <div className="row">
            <div className="col-25">
              <label htmlFor="exampleInputEmail1">Email address</label>
            </div>
            <div className="col-75">
                <input type="email" placeholder="Enter Email" refs="email" aria-describedby="emailHelp" onChange=this.handleChange.bind(this, "email") value=this.state.fields["email"]/>
                <span style=color: "red">this.state.errors["email"]</span>
            </div>
          </div>
          <div className="row">
            <div className="col-25">
                <label htmlFor="message">Message</label>
            </div>
            <div className="col-75">
                <textarea type="text" placeholder="Enter Message" rows="5" refs="message" onChange=this.handleChange.bind(this, "message") value=this.state.fields["message"]></textarea>
                <span style=color: "red">this.state.errors["message"]</span>
            </div>
          </div>
          <div className="row">
            <button type="submit" disabled=this.state.disabled>this.state.disabled ? 'Sending...' : 'Send'</button>
          </div>
      </form>
    )
  

【讨论】:

【参考方案4】:

你的 jsfiddle 不再工作了。 我已经修复它:http://jsfiddle.net/tkrotoff/bgC6E/40/ 使用 React 16 和 ES6 类。

class Adaptive_Input extends React.Component 
  handle_change(e) 
    var new_text = e.currentTarget.value;
    this.props.on_Input_Change(new_text);
  

  render() 
    return (
      <div className="adaptive_placeholder_input_container">
        <input
          className="adaptive_input"
          type="text"
          required="required"
          onChange=this.handle_change.bind(this) />
        <label
          className="adaptive_placeholder"
          alt=this.props.initial
          placeholder=this.props.focused />
      </div>
    );
  


class Form extends React.Component 
  render() 
    return (
      <form>
        <Adaptive_Input
          initial='Name Input'
          focused='Name Input'
          on_Input_Change=this.props.handle_text_input />

        <Adaptive_Input
          initial='Value 1'
          focused='Value 1'
          on_Input_Change=this.props.handle_value_1_input />

        <Adaptive_Input
          initial='Value 2'
          focused='Value 2'
          on_Input_Change=this.props.handle_value_2_input />
      </form>
    );
  


class Page extends React.Component 
  constructor(props) 
    super(props);

    this.state = 
      Name: 'No Name',
      Value_1: '0',
      Value_2: '0',
      Display_Value: '0'
    ;
  

  handle_text_input(new_text) 
    this.setState(
      Name: new_text
    );
  

  handle_value_1_input(new_value) 
    new_value = parseInt(new_value);
    var updated_display = new_value + parseInt(this.state.Value_2);
    updated_display = updated_display.toString();
    this.setState(
      Value_1: new_value,
      Display_Value: updated_display
    );
  

  handle_value_2_input(new_value) 
    new_value = parseInt(new_value);
    var updated_display = parseInt(this.state.Value_1) + new_value;
    updated_display = updated_display.toString();
    this.setState(
      Value_2: new_value,
      Display_Value: updated_display
    );
  

  render() 
    return(
      <div>
        <h2>this.state.Name</h2>
        <h2>Value 1 + Value 2 = this.state.Display_Value</h2>
        <Form
          handle_text_input=this.handle_text_input.bind(this)
          handle_value_1_input=this.handle_value_1_input.bind(this)
          handle_value_2_input=this.handle_value_2_input.bind(this)
        />
      </div>
    );
  


ReactDOM.render(<Page />, document.getElementById('app'));

由于这个库,现在相同的代码被表单验证破解了:https://github.com/tkrotoff/react-form-with-constraints => http://jsfiddle.net/tkrotoff/k4qa4heg/

const  FormWithConstraints, FieldFeedbacks, FieldFeedback  = ReactFormWithConstraints;

class Adaptive_Input extends React.Component 
  static contextTypes = 
    form: PropTypes.object.isRequired
  ;

  constructor(props) 
    super(props);

    this.state = 
      field: undefined
    ;

    this.fieldWillValidate = this.fieldWillValidate.bind(this);
    this.fieldDidValidate = this.fieldDidValidate.bind(this);
  

  componentWillMount() 
    this.context.form.addFieldWillValidateEventListener(this.fieldWillValidate);
    this.context.form.addFieldDidValidateEventListener(this.fieldDidValidate);
  

  componentWillUnmount() 
    this.context.form.removeFieldWillValidateEventListener(this.fieldWillValidate);
    this.context.form.removeFieldDidValidateEventListener(this.fieldDidValidate);
  

  fieldWillValidate(fieldName) 
    if (fieldName === this.props.name) this.setState(field: undefined);
  

  fieldDidValidate(field) 
    if (field.name === this.props.name) this.setState(field);
  

  handle_change(e) 
    var new_text = e.currentTarget.value;
    this.props.on_Input_Change(e, new_text);
  

  render() 
    const  field  = this.state;
    let className = 'adaptive_placeholder_input_container';
    if (field !== undefined) 
      if (field.hasErrors()) className += ' error';
      if (field.hasWarnings()) className += ' warning';
    

    return (
      <div className=className>
        <input
          type=this.props.type
          name=this.props.name
          className="adaptive_input"
          required
          onChange=this.handle_change.bind(this) />
        <label
          className="adaptive_placeholder"
          alt=this.props.initial
          placeholder=this.props.focused />
      </div>
    );
  


class Form extends React.Component 
  constructor(props) 
    super(props);

    this.state = 
      Name: 'No Name',
      Value_1: '0',
      Value_2: '0',
      Display_Value: '0'
    ;
  

  handle_text_input(e, new_text) 
    this.form.validateFields(e.currentTarget);

    this.setState(
      Name: new_text
    );
  

  handle_value_1_input(e, new_value) 
    this.form.validateFields(e.currentTarget);

    if (this.form.isValid()) 
      new_value = parseInt(new_value);
      var updated_display = new_value + parseInt(this.state.Value_2);
      updated_display = updated_display.toString();
      this.setState(
        Value_1: new_value,
        Display_Value: updated_display
      );
    
    else 
      this.setState(
        Display_Value: 'Error'
      );
    
  

  handle_value_2_input(e, new_value) 
    this.form.validateFields(e.currentTarget);

    if (this.form.isValid()) 
      new_value = parseInt(new_value);
      var updated_display = parseInt(this.state.Value_1) + new_value;
      updated_display = updated_display.toString();
      this.setState(
        Value_2: new_value,
        Display_Value: updated_display
      );
    
    else 
      this.setState(
        Display_Value: 'Error'
      );
    
  

  render() 
    return(
      <div>
        <h2>Name: this.state.Name</h2>
        <h2>Value 1 + Value 2 = this.state.Display_Value</h2>

        <FormWithConstraints ref=form => this.form = form noValidate>
          <Adaptive_Input
            type="text"
            name="name_input"
            initial='Name Input'
            focused='Name Input'
            on_Input_Change=this.handle_text_input.bind(this) />
          <FieldFeedbacks for="name_input">
            <FieldFeedback when="*" error />
            <FieldFeedback when=value => !/^\w+$/.test(value) warning>Should only contain alphanumeric characters</FieldFeedback>
          </FieldFeedbacks>

          <Adaptive_Input
            type="number"
            name="value_1_input"
            initial='Value 1'
            focused='Value 1'
            on_Input_Change=this.handle_value_1_input.bind(this) />
          <FieldFeedbacks for="value_1_input">
            <FieldFeedback when="*" />
          </FieldFeedbacks>

          <Adaptive_Input
            type="number"
            name="value_2_input"
            initial='Value 2'
            focused='Value 2'
            on_Input_Change=this.handle_value_2_input.bind(this) />
          <FieldFeedbacks for="value_2_input">
            <FieldFeedback when="*" />
          </FieldFeedbacks>
        </FormWithConstraints>
      </div>
    );
  


ReactDOM.render(<Form />, document.getElementById('app'));

这里提出的解决方案是骇人听闻的,因为我试图让它接近原始的 jsfiddle。要使用 react-form-with-constraints 进行正确的表单验证,请查看https://github.com/tkrotoff/react-form-with-constraints#examples

【讨论】:

【参考方案5】:

又一次解决同样的问题 - form-container on npm

【讨论】:

【参考方案6】:

我最近花了一周时间研究了很多解决方案来验证我在应用中的表单。我从所有最受瞩目的人开始,但我找不到按预期工作的人。几天后,我变得非常沮丧,直到我发现了一个非常新的和惊人的插件: https://github.com/kettanaito/react-advanced-form

开发人员反应迅速,经过我的研究,他的解决方案值得成为我认为最受关注的解决方案。我希望它可以帮助你,你会感激的。

【讨论】:

【参考方案7】:

我写了This library,它允许您包装表单元素组件,并允许您以以下格式定义验证器:-

<Validation group="myGroup1"
    validators=[
            
             validator: (val) => !validator.isEmpty(val),
             errorMessage: "Cannot be left empty"
            ,...
        ]>
            <TextField value=this.state.value
                       className=styles.inputStyles
                       onChange=
                        (evt)=>
                          console.log("you have typed: ", evt.target.value);
                        
                       />
</Validation>

【讨论】:

【参考方案8】:

您可以使用npm install --save redux-form

我正在编写一个简单的电子邮件和提交按钮表单,用于验证电子邮件并提交表单。使用 redux-form,表单默认在 html onSubmit 操作上运行 event.preventDefault()。

import React, Component from 'react';
import reduxForm from 'redux-form';

class LoginForm extends Component 
  onSubmit(props) 
    //do your submit stuff
  


  render() 
    const fields: email, handleSubmit = this.props;

    return (
      <form onSubmit=handleSubmit(this.onSubmit.bind(this))>
        <input type="text" placeholder="Email"
               className=`form-control $email.touched && email.invalid ? 'has-error' : '' `
          ...email
        />
          <span className="text-help">
            email.touched ? email.error : ''
          </span>
        <input type="submit"/>
      </form>
    );
  


function validation(values) 
  const errors = ;
  const emailPattern = /(.+)@(.+)2,\.(.+)2,/;
  if (!emailPattern.test(values.email)) 
    errors.email = 'Enter a valid email';
  

  return errors;


LoginForm = reduxForm(
  form: 'LoginForm',
  fields: ['email'],
  validate: validation
, null, null)(LoginForm);

export default LoginForm;

【讨论】:

那又怎样?人们仍然会提出这个问题,如果有解决问题的新方法,为什么不呢? Redux 与否 - 它完全有效。【参考方案9】:

首先,我将在下面提到一个示例:http://jsbin.com/rixido/2/edit

如何使用 React.JS 正确验证输入值?

随心所欲。 React 用于渲染数据模型。数据模型应该知道什么是有效的。您可以使用 Backbone 模型、JSON 数据或任何您想要表示数据及其错误状态的东西。

更具体地说:

React 通常与您的数据无关。它用于渲染和处理事件。

遵循的规则是:

    元素可以改变它们的状态。 他们不能改变道具。 他们可以调用回调来更改***道具。

如何决定某个东西应该是道具还是状态?考虑一下:除了文本字段之外,您的应用程序的任何部分都想知道输入的值是错误的吗?如果不是,则使其成为一个状态。如果是,它应该是一个道具。

例如,如果您想要一个单独的视图来呈现“您在此页面上有 2 个错误”。那么***数据模型必须知道您的错误。

该错误应该出现在哪里? 如果您的应用程序正在渲染 Backbone 模型(例如),则模型本身将具有您可以使用的 validate() 方法和 validateError 属性。您可以渲染其他可以执行相同操作的智能对象。 React 还说尽量减少 props 并生成其余数据。因此,如果您有一个验证器(例如 https://github.com/flatiron/revalidator),那么您的验证可能会逐渐减少,任何组件都可以使用匹配的验证检查 props 以查看它是否有效。

这在很大程度上取决于您。

(我个人使用 Backbone 模型并在 React 中渲染它们。我有一个***错误警报,我会在任何地方显示错误,并描述错误。)

【讨论】:

'React 还说尽量减少 props' - 你知道 React 在哪里记录这个吗? “找出应用程序状态的绝对最小表示需要是什么,并按需计算您需要的所有其他内容。” facebook.github.io/react/docs/thinking-in-react.html 第三步 不应该是“React 也说尽量将状态保持在最低限度”吗? @MosesLee 使用道具而不是组件状态将每个组件的状态保持在最低限度。在渲染调用中计算派生值而不是存储它们,将道具(和状态)保持在最低限度。我们试图在两种意义上都将 state 保持在最低限度,在第二种意义上将 props 保持在最低限度。

以上是关于如何使用 React.JS 正确验证输入值?的主要内容,如果未能解决你的问题,请参考以下文章

用ant-design在react js中提交后清除表单输入字段值

React JS - 如何通过 fetch 语句验证凭据

如何在 React Js 中编辑输入值?

如何从 React.js 中的多个输入输入字段创建列表?

React js,如何在输入更改时更新状态对象值? [复制]

如何在 React js 中单击时更改 div 背景?