React 表单绑定(受控组件和非受控组件)

291 阅读7分钟

受控组件与非受控组件

对于页面上元素的属性值,React 提供了两种方式来管理这些元素的值。比如常见的表单元素,React 提供了两种方式来管理表单元素的输入。受控组件和非受控组件,这两种组件在处理输入数据的方式上有着根本的区别。

受控组件

受控组件是指表单元素的输入值由 React 组件的状态(state)进行控制。在受控组件中,表单元素的值由组件的状态驱动,任何对输入值更改的操作都需要通过状态更新来实现。

受控组件的值由props或state传入,每当用户输入数据时,组件会通过事件处理函数更新状态,并将新的状态传递给表单元素。

假如组件没有绑定事件处理函数改变组件的值,用户的输入是不会起到任何效果的,这也就是受控的含义所在。

受控组件的两个要点:

  • 表单元素的 value 属性与组件的state状态进行绑定
  • 组件内声明了事件处理value的变化

代码示例

import { PureComponent } from 'react'

class App extends PureComponent {
  state = {
    msg:"",
  }
  changeHandle = (e) => {
    console.log(e.target.value);
  }
  render() {
    return (
      <div>
        <input type="text" value={this.state.msg} onChange={this.changeHandle} />
      </div>
    )
  }
}

export default App;

2.gif

没有使用setState函数来更新数据所以页面不会刷新并渲染用户输入的数据。

事件处理函数中调用setState更新状态

import { PureComponent } from 'react'

class App extends PureComponent {
  state = {
    msg: "",
  }
  changeHandle = (e) => {
    console.log(e.target.value);
    this.setState({
      msg:e.target.value
    });
  }
  render() {
    return (
      <div>
        <input type="text" value={this.state.msg} onChange={this.changeHandle} />
      </div>
    )
  }
}

export default App;

2.gif

在上面这个案例中:

  • msg是一个状态(this.state.msg),用于存储输入框的值
  • 表单元素的value 属性绑定了 this.state.msg,使得输入框的值由状态控制
  • onChange 事件的事件处理函数(this.changeHandle)在每次输入时更新状态,确保组件的状态与用户输入保持同步

非受控组件

非受控组件是指表单元素的输入值不由 React 组件的状态控制,而是通过直接操作 DOM 元素来获取输入值。

在非受控组件中,表单输入的值存在于 DOM 元素中,而不是 React 组件的状态。要获取当前的输入值,通常使用 React 的 ref 来访问 DOM 元素并获取其值。

代码示例

import { PureComponent,createRef } from 'react'

class App extends PureComponent {
  // 创建 ref
  inputRef = createRef();
  handleButtonClick = () => {
    // 通过 ref 获取输入框的值
    const inputValue = this.inputRef.current.value;
    console.log('输入框的值:', inputValue);
  }
  render() {
    return (
      <div>
        <input type="text" ref={this.inputRef} />
        <button onClick={this.handleButtonClick}>获取输入框值</button>
      </div>
    )
  }
}

export default App;

image.png

在上面这个案例中:

  • 使用 createRef 创建了一个 inputRef,用于引用输入框
  • 在 handleButtonClick函数中,通过 this.inputRef.current.value 获取当前输入框的值

受控组件与非受控组件的对比

受控组件的优点:

  • 单一数据源:所有表单数据都存储在组件的状态中,易于管理和维护
  • 可控性:开发者可以轻松地验证输入、清空表单等操作,便于实现复杂的表单逻辑
  • 实时反馈:可以在输入过程中实现即时反馈,增强用户体验

受控组件的缺点:

  • 性能开销:对于大量表单元素的复杂组件,频繁更新状态可能导致性能问题
  • 繁杂的代码:需要编写额外的事件处理逻辑,可能使代码变得冗长

受控组件的适用场景:

  • 需要实时反馈:当需要根据用户输入即时更新其他界面元素时,受控组件更为合适
  • 表单验证:对于需要复杂验证逻辑的表单,使用受控组件可以更方便地进行状态管理
  • 动态表单:当表单字段的数量或类型是动态变化时,使用受控组件可以更好地管理状态

非受控组件的优点:

  • 简单实现:不需要额外的状态管理,代码相对简洁
  • 适用于复杂表单:在某些情况下,不需要实时更新状态,使用非受控组件能够减少渲染次数

非受控组件的缺点:

  • 难以维护:由于数据不在组件的状态中,跟踪输入变化变得困难
  • 缺乏实时反馈:无法实现即时验证或反馈,用户体验可能受到影响
  • 违背 React 的理念:非受控组件使用了直接操作 DOM 的方式,违背了 React 的声明式编程理念

非受控组件的适用场景:

  • 简单表单:对于简单的表单,只需要提交数据而不需要实时更新状态时,非受控组件可以减少代码复杂性
  • 不需要实时验证:如果不需要对用户输入进行实时验证,非受控组件可以更轻松地管理

常见的表单组件的绑定

在组件中声明表单元素input,textarea,select等时,一般都要为表单元素传入应用状态中的值,然后根据表单元素中用户的输入,对应用数据进行相应的操作和改变。常见的表单包括输入框,单选框,复选框,下拉框和多文本框,在Vue中只需要使用v-model指令,但是在React中需要自己实现双向数据的绑定。

输入框

先给input框的value绑定一个值,然后通过change事件来获取用户输入的值,并将这个值赋值给value属性绑定的值,再使用setState方法来重新渲染组件就可以实现数据的双向绑定了,最后表单收集的就是用户输入的value值。

import { PureComponent } from 'react'

class App extends PureComponent {
  state = {
    msg: "",
  }
  changeHandle = (e) => {
    console.log(e.target.value);
    this.setState({
      msg:e.target.value
    });
  }
  render() {
    return (
      <div>
        <input type="text" value={this.state.msg} onChange={this.changeHandle} />
      </div>
    )
  }
}

export default App;

2.gif

在react中onInput事件是在用户更改值时立即触发。在 React 中习惯于使用onChange事件,onChange事件在用户更改输入框的值时立即触发,每次按键时都会触发。行为类似于浏览器的 input 事件。

在vue中@change事件会在表单元素的值发生变化并失去焦点(通常是用户输入后按下回车键或点击其他地方)时触发。而@input事件是在每次输入时都会触发。这类似于原生的inputchange事件。

单选框和复选框

radio单选框和checkbox复选框通过操作checked属性实现受(checkedtrue表示选中,checkedfalse表示未选中),value属性只起辅助作用。

单选框

单选框可以通过onChange事件获取value的值,根据value值间接控制是否选中,但是onChange只能获取value值并不能改变value值,将选中的value值赋值给表单要收集的数据变量就可以收集到选中的value值。

import { PureComponent } from 'react'

class App extends PureComponent {
  state = {
    sex: "",
  }
  sexhandle = (e) => {
    console.log(e.target.value);
    this.setState({
      sex: e.target.value
    });
  }
  render() {
    return (
      <div>
        <label>
          <input type="radio" name="sex" value='男' checked={this.state.sex == '男'} onChange={this.sexhandle} /></label>
        <label>
          <input type="radio" name='sex' value='女' checked={this.state.sex == '女'} onChange={this.sexhandle} /></label>
      </div>
    )
  }
}

export default App;

2.gif

复选框

复选框checked属性是对数组进行操作,通过一个state状态中的数组来存储变更。也是通过value值间接控制是否选中,onChange同样只能获取value值,不能改变,在数组中收集选中的value值。

import { Component } from 'react'

class App extends Component {
  state = {
    hobby: [],
  }
  hobbyhandle = (e) => {
    console.log(e.target.value, 88);
    if(this.state.hobby.includes(e.target.value)){
      // 已选中的再次点击取消选中,将value从数组中取消
      this.state.hobby.splice(this.state.hobby.indexOf(e.target.value),1);
    }else{
      // 未被选中的添加到数组中
      this.state.hobby.push(e.target.value)
    }
    console.log(this.state.hobby, 111);
    this.setState(this.state);
  }
  render() {
    return (
      <div>
        <label>
          <input type="checkbox" value='篮球' checked={this.state.hobby.includes('篮球')} onChange={this.hobbyhandle} />篮球
        </label>
        <label>
          <input type="checkbox" value='高尔夫' checked={this.state.hobby.includes('高尔夫')} onChange={this.hobbyhandle} />高尔夫
        </label>
        <label>
          <input type="checkbox" value='乒乓球' checked={this.state.hobby.includes('乒乓球')} onChange={this.hobbyhandle} />乒乓球
        </label>
        <label>
          <input type="checkbox" value='躺平摆烂' checked={this.state.hobby.includes('躺平摆烂')} onChange={this.hobbyhandle} />躺平摆烂
        </label>
      </div>
    )
  }
}

export default App;

2.gif

select下拉框

关于select,可以通过循环渲染option,在之前的select中,value都是写在option上的,但是在React中,对select进行了简单的封装,需要将value放在select中就可以通过select的onChange事件获取当前选择的value

import { Component } from 'react'

class App extends Component {
  state = {
    city: "",
  }
  cityarr = ["北京","深圳","杭州","南京","上海"]
  cityhandle = (e) => {
  this.state.city = e.target.value;
  console.log(this.state.city, 111);
  this.setState(this.state);
};
render() {
  return (
    <div>
      <select value={this.state.city} onChange={this.cityhandle}>
        {
          this.cityarr.map((value, index) => {
            return <option key={index}>{value}</option>
          })
        }
      </select>
    </div>
  )
}
}

export default App;

2.gif

日期选择器

收集的是value值,就是选择的日期

import { PureComponent } from 'react'

class App extends PureComponent {
  state = {
    date: "",
  }
  datehandle = (e) => {
    this.setState({
      date: e.target.value
    }, () => {
      console.log(this.state.date, 111)
    });
  };
  render() {
    return (
      <div>
        <input type="date" value={this.state.date} onChange={this.datehandle} />
      </div>
    )
  }
}

export default App;

2.gif

文件类型

通过e.target.file来获取用户选择的文件,它是一个数组。

import { PureComponent } from 'react'

class App extends PureComponent {
  state = {
    file: [],
  }
  filehandle = (e) => {
    console.log(e.target.files);
    this.setState({
      file: [...this.state.file,e.target.files[0]]
    }, () => {
      console.log(this.state.file, 111)
    });
  };
  render() {
    return (
      <div>
        <input type="file" onChange={this.filehandle}/>
      </div>
    )
  }
}

export default App;

image.png

文本框

文本框和输入框的text同理,收集的是value就是用户输入的内容。

import { PureComponent } from 'react'

class App extends PureComponent {
  state = {
    text: ""
  }
  texthandle = (e) => {
    this.setState({
      text: e.target.value
    }, () => {
      console.log(this.state.text, 111)
    });
  };
  render() {
    return (
      <div>
        <textarea value={this.state.text} onChange={this.texthandle}></textarea>
      </div>
    )
  }
}

export default App;

2.gif