由一行文本输入框引起的思考

  文章是关于React组件之表单单行文本输入框的一些思考。可能你们第一反应都是,不就是一行<input/>嘛,没什么特别的吧?若是说到输入框的的话,可能圈子里上大多数封装好的ReactUI组件库中使用的方式无非都是在组件中经过 Props传值给Input组件,而后在Input组件的onChange回掉中改变当前组件的State,从而达到改变Input组件Props的目的。html

  这里引起一个思考,若是父组件相对复杂,当其中一个Input组件的值被改变时,会更新State触发render和其余全部可能与State相关的子组件(这些字组件的Prop都是经过父组件的State来传递的)的更新,简单的想象一下,咱们在输入框中每敲一下键盘都会触发一系列的代码逻辑,这可能会是多恐怖的一件事。node

经过Props传值的必要性

  Input输入框必然是有value这一属性的,若是这个属性是做为State或者Props传递的,要改变这一属性,也是必然经过改变Props或者State来实现,而改变的途径有且只有一个,就是在Input的onChange里面进行监听,正如官方所给的示例代码同样Forms-React:react

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('A name was submitted: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}复制代码

  若是咱们想要把这个Input输入框封装成为一个单独的组件,按照咱们正常的处理逻辑,这个组件的value必定是经过Props传递过来的,当咱们把这个Props做为Input的属性值以后,想要改变的话也就只能经过父组件改变传递的Props来实现了,不可避免的产生了一些改变值和传递值的代码逻辑。git

一些优秀UI库的常规处理

  了解了Input输入框的传值机制以后,咱们来看看圈子里一些优秀组件库的处理方式是否和咱们预想的一致,是否也有这些不可变的代码逻辑问题。github

React-Bootstrap

  React-Boostrap算是Bootstrap的React版,其权威性也是可见一斑。官方的示例代码也是彻底和咱们预期的一致,经过onChang回掉来改变父组件的State,以后再把State做为Props传递给子组件,有兴趣可自行查阅。而咱们打开源码来看,关键部分以下:bootstrap

class FormControl extends React.Component {
  render() {
      ...
    const {
      componentClass: Component,
      type,
      id = controlId,
      inputRef,
      className,
      bsSize,
      ...props
    } = this.props;

    const [bsProps, elementProps] = splitBsProps(props);
    ...
    return (
      <Component {...elementProps} type={type} id={id} ref={inputRef} className={classNames(className, classes)} /> ); } }复制代码

  能够解读成就是一个标签的封装,没有本身的State,其值彻底依赖于Props的改变,这也和咱们预期的结果吻合,而温和的结果就是咱们最开始抛出的一些疑问,看来在这里也没获得解决。antd

ant-design

  对比一下国人写的ant-design,直接看官方示例代码,当看到这里的时候是否又些小激动呢,代码以下:框架

import { Input } from 'antd';

ReactDOM.render(<Input placeholder="Basic usage" />, mountNode);复制代码

  彷佛摆脱了在回掉的onChange中改变State来传递Props的方式,代码简洁明了。打开源码来看看,ant-design,截取关键部分代码:ide

renderInput() {
    const { value, className } = this.props;
    const otherProps = omit(this.props, [
      'prefixCls',
      'onPressEnter',
      'addonBefore',
      'addonAfter',
      'prefix',
      'suffix',
    ]);

    if ('value' in this.props) {
      otherProps.value = fixControlledValue(value);
      // Input elements must be either controlled or uncontrolled,
      // specify either the value prop, or the defaultValue prop, but not both.
      delete otherProps.defaultValue;
    }
    return this.renderLabeledIcon(
      <input {...otherProps} className={classNames(this.getInputClassName(), className)} onKeyDown={this.handleKeyDown} ref="input" />, ); }复制代码

  能够很明显的看到,整个组件也并无State,全部的状态也都是经过Props来进行管理的,并无看出其奇特之处在哪儿。若是咱们在官方示例中加入一个属性,value="xxxx",如此一来,想要改变这个值也是和以前的处理一模一样。函数

react-ui

  也是国人写的一个框架,相对来讲名气较小。直接打开其源码来看,react-ui,关键代码以下:

constructor (props) {
    super(props)
    this.handleChange = this.handleChange.bind(this)
  }

  handleChange (event) {
    const { type } = this.props

    let value = event.target.value
    if (value && (type === 'integer' || type === 'number')) {
      if (!Regs[type].test(value)) return
    }
    this.props.onChange(value)
  }复制代码

  很常规的一种处理,和上面的处理方式惟一不一样在于语法上面的一些差别,可能这也并不是咱们想要看到的。

  是否真的有那么一种处理机制和咱们预想不同,而且可以解决咱们的疑问呢?

MUI

  直接打开源码来看,MUI,关键部分以下:

constructor(props) {
    super(props);
    let value = props.value;
    let innerValue = value || props.defaultValue;

    if (innerValue === undefined) innerValue = '';

    this.state = {
      innerValue: innerValue,
      isTouched: false,
      isPristine: true
    };
    ...
  }
  componentWillReceiveProps(nextProps) {
    if ('value' in nextProps) this.setState({ innerValue: nextProps.value });
  }
  onChange(ev) {
    this.setState({
      innerValue: ev.target.value,
      isPristine: false
    });
    ...
  }复制代码

  能够看到,这个UI的处理方式和以前所见就都有所不一样了,不一样之处在于,把传入进来的Props.value又作了额外的处理,塞到State里面去了,这样改变input value的方式就能够只改变当前这个组件的state来进行了,而不须要再次经过改变回掉到父组件改变State,从新传入Props的方式来进行。看到这里是否是有一丝的欣慰呢?可是冷静一想,好像是把问题搞复杂了:

  • 为何须要经过计算传入Props来看成State呢,既然能够经过Props计算获得,拿这个属性应该不是一个State;
  • componentWillReceiveProps中又要处理判断一些逻辑,并且这个逻辑颇有可能要与本身的业务相关,从而来决定是否须要改变State,这些逻辑并不是是咱们想要看到的。

metarial-design

  所谓扁平化至极的风格,看看源码里面究竟怎么处理这个问题呢,metarial-design:

handleRefInput = node => {
    this.input = node;
    if (this.props.inputRef) {
      this.props.inputRef(node);
    }
  };

let inputProps = {
      ref: this.handleRefInput,
      ...inputPropsProp,
    };

    return (
      <div onBlur={this.handleBlur} onFocus={this.handleFocus} className={className} {...other}> <InputComponent {...inputProps} /> </div> );复制代码

  其中有一处关键处理和以前看到的都不同,这个ref属性,直接赋值this.input=input(固然它还有额外的函数处理,这里并不影响),这行代码究竟有什么神奇的效果嘛,应该只是单纯的直接从DOM里面取值或者进行一些直接修改DOM的操做吧?。最后看看官方有没有相关的解释。

React官网

  仔细查阅,找到了一篇相关的文章说明Uncontroled Components。这篇文章详细的介绍咱们的疑惑(看来这些疑问并不是是想固然的,实实在在有这个问题)。

In most cases, we recommend using controlled components to implement forms. In a controlled component, form data is handled by a React component. The alternative is uncontrolled components, where form data is handled by the DOM itself.

  看到这里应该就豁然开朗了,因此所谓正确的姿式:

render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" ref={(input) => this.input = input} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }复制代码

  看了以后,就能理解material-design和ant-design的写法了,若是想要默认值,正如官方所说:

In the React rendering lifecycle, the value attribute on form elements will override the value in the DOM. With an uncontrolled component, you often want React to specify the initial value, but leave subsequent updates uncontrolled. To handle this case, you can specify a defaultValue attribute instead of value.

render() {
  return (
    <form onSubmit={this.handleSubmit}>
      <label>
        Name:
        <input
          defaultValue="Bob"
          type="text"
          ref={(input) => this.input = input} />
      </label>
      <input type="submit" value="Submit" />
    </form>
  );
}复制代码

  来回往复,直接一个defaultValue解决了咱们的疑惑,本来就在官方网站摆着却看似明白不清楚具体场景,想的太多作的太少。

相关文章
相关标签/搜索