【学习记录】React之受控与非受控组件

668 阅读3分钟

React中一个奇怪的错误

学习React的时候遇到了一个Input的值无法修改的现象

<input type="text" value='admin'/>

这个标签在原生js中可以修改,但是在React组件无法修改,并且控制台报错,提示我们要给Input提供一个onChange事件,或者用defaultValue代替value,(⊙o⊙)?好好的原生DOM的value到React中就报错了呢?

You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`.

image.png 运行下面代码会发现页面无法修改输入框的值

export default class App extends Component {

  username = React.createRef()

  render() {
    return (
      <div>
        <h1>登录页</h1>
        <input type="text" value='admin'  ref={this.username}/>
        <button onClick={() => { console.log(this.username.current.value)}}>登录</button>
      </div>
    )
  }
}

我的理解:造成这种现象的原因,React封装了原生HTML的表单元素,可以封装后的表单元素理解为一个组件,封装后的value已经不是原生DOM的value属性,而defaultValue才是原生DOM的value属性。

明白这个错误后,可以引入受控与非受控组件了。

受控与非受控组件

简单理解

  • 受控:组件的状态由React控制
  • 非受控:组件的状态由原生DOM控制

受控组件

如果将React的state与表单的value值建立依赖关系,然后通过onChange事件或onInput事件调用setState({})修改state属性,就能控制用户输入表单这一动作了,被React这一方式来控制表单元素输入值的组件就是受控组件。talk is cheep,show me the code!

export default class App extends Component {

  state = { username: 'admin' }

  change = (event) => {
    this.setState({
      username: event.target.value
    })
  }

  render() {
    return (
      <div>
        <h1>登录页</h1>
        <input type="text" value={this.state.username} onChange={this.change} />
        <button onClick={() => { console.log(this.state.username) }}>登录</button>
      </div>
    )
  }
}

上面的代码只修改两处:1.将value绑定为state,2.绑定onChange事件,注意:这里onChange等同与原生DOM事件的onInput,每次改变value值都会触发一个onChange事件,无需失去焦点。

好了,我们已经实现了一个最最最简单的受控组件。学过Vue的小伙伴表示无语,这不是Vue中的双向绑定嘛。

受控组件的运行流程

graph TD
用户输入A --> 触发onChange事件 --> onChange中setState设置state.username='A' --> 渲染Input更新页面

非受控组件的运行流程

graph TD
用户输入A -->  Input更新显示A

上面流程之间的对比,很明显的体现出受控与非受控之间的差异,React推荐使用受控组件,因为我们可以更好的控制整个流程,这样我们可以在流程中做一个修改等操作。

select受控组件

单选select

单选select受控组件与原生HTML有一点不同,原生HTML的默认选中是在option中加上selected属性, 而受控组件直接在select根标签上加入value属性,value属性就是选中的值,流程与input相同

<select>
          <option value="CF">穿越火线</option>
          <option value="LOL">英雄联盟</option>
          <option value="DNF">地下城与勇士</option>
          <option value="CSOL" selected>反恐精英OL</option>
</select>
export default class App extends Component {

  state = { game: 'CSOL' }

  change = (event) => {
    this.setState({
      game: event.target.value
    })
  }

  render() {
    return (
      <div>
        <select value={this.state.game} onChange={this.change}>
          <option value="CF">穿越火线</option>
          <option value="LOL">英雄联盟</option>
          <option value="DNF">地下城与勇士</option>
          <option value="CSOL">反恐精英OL</option>
        </select>
        <button onClick={()=>{console.log(this.state.game)}}>输出表单内容</button>
      </div>
    )
  }
}

多选select

多选select受控组件在单选select受控组件上多加两点:

  1. select标签加上multiple={true}属性
  2. 将state.xxx的值改为数组

参考https://juejin.cn/post/6858276396968951822#heading-7