Javascript 为什么我的表单输入在重新渲染后只注册一个字符?

Javascript 为什么我的表单输入在重新渲染后只注册一个字符?,javascript,reactjs,input,frontend,Javascript,Reactjs,Input,Frontend,在初始渲染时,输入组件将正常工作。然而,当它不再聚焦和重新聚焦后,它将只接受一个字符。如果我重置表单,也会发生这种情况 我创建了一个FormValidationStateManager类来重用所需的Joi验证逻辑 Form.jsx class Form extends Component { constructor(props) { super(props); this.handleSubmit = this.handleSubmit.bind(this); this

在初始渲染时,输入组件将正常工作。然而,当它不再聚焦和重新聚焦后,它将只接受一个字符。如果我重置表单,也会发生这种情况

我创建了一个FormValidationStateManager类来重用所需的Joi验证逻辑

Form.jsx

class Form extends Component {
  constructor(props) {
    super(props);
    this.handleSubmit = this.handleSubmit.bind(this);
    this.validation = new FormValidationStateManager();
  }

  handleSubmit(e) {
    e.preventDefault();
    var result = this.validation.validateAll();

    this.setState({
      // need to do this, trigger render after validations
      // we could eventually encapsulate this logic into FormValidationSM.
      submit: Date.now()
    }, function() {
      if (result.valid) {
        this.props.onSubmit(result.data);
      }
    }.bind(this));
  }

  resetForm(e) {
    e.preventDefault();
    this.validation.clearForm();

    this.setState({
      // need to do this, trigger render after validations,
      // we could eventually encapsulate this logic into FormValidationSM.
      submit: Date.now()
    });
  }

  render() {
    // TODO implement logic for rendering name from API call
    let name = "John Doe";
    return (
      <form class='info-form' onSubmit={this.handleSubmit} autocomplete='off'>
        <div class='info-block-section-title'>
          <h2>Welcome {name}</h2>
          <h4>Some nicely worded few sentences that let's the user know they just need to fill out a few bits of information to start the process of getting their student loans paid down!</h4>
        </div>

        <br />

        <div class='info-block-section-body'>
          <InfoFieldset validation={this.validation} />
        </div>
        <div class='info-form-button'>
          <FormButton label="Continue" />
          <FormButton type="button" onClick={this.resetForm.bind(this)} label="Reset" />
        </div>
      </form>

    )
  }
}

export default Form;
class FormElementInput extends Component {
  constructor(props) {
    super(props);
    this.input = React.createRef();
    this.cachedErrors = {};
    this.cachedValue = null;
    this.state = { focused: false };
    this.validation = this.props.validation || new FormValidationStateManager();
  }

  componentDidUpdate(prevProps, prevState) {

  }

  shouldComponentUpdate(nextProps, nextState) {
    var filter = function(val, key, obj) {
      if (_.isFunction(val)) {
        return true;
      }

      // Certain props like "label" can take a React element, but those
      // are created dynamically on the fly and they have an random internal UUID,
      // which trips the deep-equality comparision with a false-positive.
      // This is wasted cycles for rendering.
      if (React.isValidElement(val)) {
        return true;
      }

      // validation is a complex obj that we shouldn't be caring about
      if (_.contains(['validation'], key)) {
        return true;
      }
    };

    // if the errors after a validation got updated, we should re-render.
    // We have to compare in this manner because the validation object is not shallow-copied
    // via props since it is managed at a higher-component level, so we need to
    // do a local cache comparision with our local copy of errors.
    if (!_.isEqual(this.cachedErrors, this.getErrors())) {
      return true;
    }

    // if the errors after a validation got updated, we should re-render.
    // We have to compare in this manner because the validation object is not shallow-copied
    // via props since it is managed at a higher-component level, so we need to
    // do a local cache comparision with our local copy of errors.
    if (this.cachedValue !== this.getDisplayValue()) {
      return true;
    }

    return !_.isEqual(_.omit(nextProps, filter), _.omit(this.props, filter)) || !_.isEqual(this.state, nextState);
  }

  setValue(value) {
    console.error('deprecated - can not set value directly on FormElementInput anymore');
    throw 'deprecated - can not set value directly on FormElementInput anymore';
  }

  isDollarType() {
    return this.props.type === 'dollar';
  }

  isRateType() {
    return this.props.type === 'rate';
  }

  getType() {
    if (this.isDollarType() || this.isRateType()) {
      return 'text';
    } else {
      return this.props.type;
    }
  }

  getPrefix() {
    if (this.isDollarType()) {
      return _.compact([this.props.prefix, '$']).join(' ');
    } else {
      return this.props.prefix;
    }
  }

  getPostfix() {
    if (this.isRateType()) {
      return _.compact([this.props.postfix, '%']).join(' ');
    } else {
      return this.props.postfix;
    }
  }

  getDisplayValue() {
    var value = this.props.value || this.validation.getFormValue(this.props.field);
      // while DOM input is focused, just return the same value being typed
      if (this.state.focused) {
        return value;
      }

      if (this.isDollarType()) {
        if (_.isUndefined(value)) {
          return value;
        }
        // keep in sync with the validation check in getOnChange()
        // even if this is different - it wont break - cause accounting.js handles gracefully
        else if (_.isNumber(value) || !value.match(/[a-z]/i)) {
          return accounting.formatMoney(value, '', 2);
        } else {
          return value;
        }
      } else if (this.isRateType()){
        if (_.isUndefined(value)) {
          return '';
        }

        return accounting.formatNumber(value, 3)
      } else {
        return value;
      }
  }

  getErrors() {
    return this.props.errors || this.validation.getValidationMessages(this.props.field);
  }

  onBlur(event) {
    this.validation.validate(this.props.field);
    this.setState({ focused: false });
  }

  onFocus(event) {
    this.setState({ focused: true });
  }

  onChange(event) {
    if (this.isDollarType()) {
      // only accept if the input at least resembles a currency
      // accounting.parse already strips alot of characters and does this silently
      // we want to be a little less strict than accounting.parse since we need to allow more
      // different types of inputs +-,. and also let accounting.parse do its job
      // very basic check here for a-z characters
      if (!event.target.value.match(/[a-z]/i)) {
        var parsedValue = accounting.parse(event.target.value);
        event._parsedValue = parsedValue;
      }
    }

    let _value = event._parsedValue || event.target.value;
    console.log(this.input)
    this.validation.setFormValue(this.props.field, _value);
  }

  _eventHandlers(field) {
    return {
      onChange: this.onChange.bind(this),
      onBlur: this.onBlur.bind(this),
      onFocus: this.onFocus.bind(this)
    };
  }

  _mergeEventHandlers(p1, p2) {
    var events = [
      'onChange',
      'onBlur',
      'onFocus'
    ];

    var r1 = {};

    events.map(function(e) {
      r1[e] = FuncUtils.mergeFunctions(p1[e], p2[e]);
    }.bind(this));

    return r1;
  }

  render() {
    let _errors = this.cachedErrors = this.getErrors();
    let _value = this.cachedValue = this.getDisplayValue();

    let attrs = {
      id: this.props.htmlId || ('field_' + this.props.field),
      className: classnames({
        'error': _errors && _errors.length
      }),
      type: this.getType(),
      placeholder: this.props.placeholder,
      autoComplete: "false",
      disabled: this.props.disabled,
      readOnly: this.props.disabled,
      value: _value,
      ref: this.input,
    };

    attrs = _.extend(attrs, this._mergeEventHandlers(this.props, this._eventHandlers()));

    const prefix = this.getPrefix();
    const postfix = this.getPostfix();

    return (
      <FormElementWrapper
        type="input"
        field={this.props.field}
        errors={_errors}
        label={this.props.label}>
        <div className="form-element-input">
          <DisplayToggle remove={true} hide={!prefix}>
            <span className="prefix">{prefix}</span>
          </DisplayToggle>
          <div className={classnames({
          "has-prefix": !!prefix,
          "has-postfix": !!postfix
        })}>
            <input {...attrs} />
          </div>
          <DisplayToggle remove={true} hide={!postfix}>
            <span className="postfix">{postfix}</span>
          </DisplayToggle>
        </div>
        <div className="form-error">
          {_errors.map((msg, idx) => {
            return (<FormError key={idx}>{msg}</FormError>)
          })}
        </div>
      </FormElementWrapper>
    )
  }
};

FormElementInput.PropTypes = {
  field: PropTypes.string.isRequired,
  id: PropTypes.string.isRequired,
  value: PropTypes.any,
  label: PropTypes.any,
  errors: PropTypes.array,
  type: PropTypes.string,
  placeholder: PropTypes.string,
  onChange: PropTypes.func,
  onFocus: PropTypes.func,
  onKeyUp: PropTypes.func,
  tooltip: PropTypes.string,
  prefix: PropTypes.string,
  postfix: PropTypes.string,
  disabled: PropTypes.bool,
  disableTrimming: PropTypes.bool
}

FormElementInput.defaultProps = {
  type: 'text',
  placeholder: 'Enter Value',
  tooltip: undefined,
  prefix: undefined,
  postfix: undefined,
  disabled: false,
  disableTrimming: false
}

export default FormElementInput;

我想我明白了。我将FormElementInput.jsx中的属性的
value:_value
更改为
defaultValue:_value

class Form extends Component {
  constructor(props) {
    super(props);
    this.handleSubmit = this.handleSubmit.bind(this);
    this.validation = new FormValidationStateManager();
  }

  handleSubmit(e) {
    e.preventDefault();
    var result = this.validation.validateAll();

    this.setState({
      // need to do this, trigger render after validations
      // we could eventually encapsulate this logic into FormValidationSM.
      submit: Date.now()
    }, function() {
      if (result.valid) {
        this.props.onSubmit(result.data);
      }
    }.bind(this));
  }

  resetForm(e) {
    e.preventDefault();
    this.validation.clearForm();

    this.setState({
      // need to do this, trigger render after validations,
      // we could eventually encapsulate this logic into FormValidationSM.
      submit: Date.now()
    });
  }

  render() {
    // TODO implement logic for rendering name from API call
    let name = "John Doe";
    return (
      <form class='info-form' onSubmit={this.handleSubmit} autocomplete='off'>
        <div class='info-block-section-title'>
          <h2>Welcome {name}</h2>
          <h4>Some nicely worded few sentences that let's the user know they just need to fill out a few bits of information to start the process of getting their student loans paid down!</h4>
        </div>

        <br />

        <div class='info-block-section-body'>
          <InfoFieldset validation={this.validation} />
        </div>
        <div class='info-form-button'>
          <FormButton label="Continue" />
          <FormButton type="button" onClick={this.resetForm.bind(this)} label="Reset" />
        </div>
      </form>

    )
  }
}

export default Form;
class FormElementInput extends Component {
  constructor(props) {
    super(props);
    this.input = React.createRef();
    this.cachedErrors = {};
    this.cachedValue = null;
    this.state = { focused: false };
    this.validation = this.props.validation || new FormValidationStateManager();
  }

  componentDidUpdate(prevProps, prevState) {

  }

  shouldComponentUpdate(nextProps, nextState) {
    var filter = function(val, key, obj) {
      if (_.isFunction(val)) {
        return true;
      }

      // Certain props like "label" can take a React element, but those
      // are created dynamically on the fly and they have an random internal UUID,
      // which trips the deep-equality comparision with a false-positive.
      // This is wasted cycles for rendering.
      if (React.isValidElement(val)) {
        return true;
      }

      // validation is a complex obj that we shouldn't be caring about
      if (_.contains(['validation'], key)) {
        return true;
      }
    };

    // if the errors after a validation got updated, we should re-render.
    // We have to compare in this manner because the validation object is not shallow-copied
    // via props since it is managed at a higher-component level, so we need to
    // do a local cache comparision with our local copy of errors.
    if (!_.isEqual(this.cachedErrors, this.getErrors())) {
      return true;
    }

    // if the errors after a validation got updated, we should re-render.
    // We have to compare in this manner because the validation object is not shallow-copied
    // via props since it is managed at a higher-component level, so we need to
    // do a local cache comparision with our local copy of errors.
    if (this.cachedValue !== this.getDisplayValue()) {
      return true;
    }

    return !_.isEqual(_.omit(nextProps, filter), _.omit(this.props, filter)) || !_.isEqual(this.state, nextState);
  }

  setValue(value) {
    console.error('deprecated - can not set value directly on FormElementInput anymore');
    throw 'deprecated - can not set value directly on FormElementInput anymore';
  }

  isDollarType() {
    return this.props.type === 'dollar';
  }

  isRateType() {
    return this.props.type === 'rate';
  }

  getType() {
    if (this.isDollarType() || this.isRateType()) {
      return 'text';
    } else {
      return this.props.type;
    }
  }

  getPrefix() {
    if (this.isDollarType()) {
      return _.compact([this.props.prefix, '$']).join(' ');
    } else {
      return this.props.prefix;
    }
  }

  getPostfix() {
    if (this.isRateType()) {
      return _.compact([this.props.postfix, '%']).join(' ');
    } else {
      return this.props.postfix;
    }
  }

  getDisplayValue() {
    var value = this.props.value || this.validation.getFormValue(this.props.field);
      // while DOM input is focused, just return the same value being typed
      if (this.state.focused) {
        return value;
      }

      if (this.isDollarType()) {
        if (_.isUndefined(value)) {
          return value;
        }
        // keep in sync with the validation check in getOnChange()
        // even if this is different - it wont break - cause accounting.js handles gracefully
        else if (_.isNumber(value) || !value.match(/[a-z]/i)) {
          return accounting.formatMoney(value, '', 2);
        } else {
          return value;
        }
      } else if (this.isRateType()){
        if (_.isUndefined(value)) {
          return '';
        }

        return accounting.formatNumber(value, 3)
      } else {
        return value;
      }
  }

  getErrors() {
    return this.props.errors || this.validation.getValidationMessages(this.props.field);
  }

  onBlur(event) {
    this.validation.validate(this.props.field);
    this.setState({ focused: false });
  }

  onFocus(event) {
    this.setState({ focused: true });
  }

  onChange(event) {
    if (this.isDollarType()) {
      // only accept if the input at least resembles a currency
      // accounting.parse already strips alot of characters and does this silently
      // we want to be a little less strict than accounting.parse since we need to allow more
      // different types of inputs +-,. and also let accounting.parse do its job
      // very basic check here for a-z characters
      if (!event.target.value.match(/[a-z]/i)) {
        var parsedValue = accounting.parse(event.target.value);
        event._parsedValue = parsedValue;
      }
    }

    let _value = event._parsedValue || event.target.value;
    console.log(this.input)
    this.validation.setFormValue(this.props.field, _value);
  }

  _eventHandlers(field) {
    return {
      onChange: this.onChange.bind(this),
      onBlur: this.onBlur.bind(this),
      onFocus: this.onFocus.bind(this)
    };
  }

  _mergeEventHandlers(p1, p2) {
    var events = [
      'onChange',
      'onBlur',
      'onFocus'
    ];

    var r1 = {};

    events.map(function(e) {
      r1[e] = FuncUtils.mergeFunctions(p1[e], p2[e]);
    }.bind(this));

    return r1;
  }

  render() {
    let _errors = this.cachedErrors = this.getErrors();
    let _value = this.cachedValue = this.getDisplayValue();

    let attrs = {
      id: this.props.htmlId || ('field_' + this.props.field),
      className: classnames({
        'error': _errors && _errors.length
      }),
      type: this.getType(),
      placeholder: this.props.placeholder,
      autoComplete: "false",
      disabled: this.props.disabled,
      readOnly: this.props.disabled,
      value: _value,
      ref: this.input,
    };

    attrs = _.extend(attrs, this._mergeEventHandlers(this.props, this._eventHandlers()));

    const prefix = this.getPrefix();
    const postfix = this.getPostfix();

    return (
      <FormElementWrapper
        type="input"
        field={this.props.field}
        errors={_errors}
        label={this.props.label}>
        <div className="form-element-input">
          <DisplayToggle remove={true} hide={!prefix}>
            <span className="prefix">{prefix}</span>
          </DisplayToggle>
          <div className={classnames({
          "has-prefix": !!prefix,
          "has-postfix": !!postfix
        })}>
            <input {...attrs} />
          </div>
          <DisplayToggle remove={true} hide={!postfix}>
            <span className="postfix">{postfix}</span>
          </DisplayToggle>
        </div>
        <div className="form-error">
          {_errors.map((msg, idx) => {
            return (<FormError key={idx}>{msg}</FormError>)
          })}
        </div>
      </FormElementWrapper>
    )
  }
};

FormElementInput.PropTypes = {
  field: PropTypes.string.isRequired,
  id: PropTypes.string.isRequired,
  value: PropTypes.any,
  label: PropTypes.any,
  errors: PropTypes.array,
  type: PropTypes.string,
  placeholder: PropTypes.string,
  onChange: PropTypes.func,
  onFocus: PropTypes.func,
  onKeyUp: PropTypes.func,
  tooltip: PropTypes.string,
  prefix: PropTypes.string,
  postfix: PropTypes.string,
  disabled: PropTypes.bool,
  disableTrimming: PropTypes.bool
}

FormElementInput.defaultProps = {
  type: 'text',
  placeholder: 'Enter Value',
  tooltip: undefined,
  prefix: undefined,
  postfix: undefined,
  disabled: false,
  disableTrimming: false
}

export default FormElementInput;
看到了吗

有人知道为什么要把它固定在引擎盖下吗