2025年3月14日 星期五 甲辰(龙)年 月十三 设为首页 加入收藏
rss
您当前的位置:首页 > 计算机 > 编程开发 > JavaScript

React 表单详解

时间:02-04来源:作者:点击数:23

前言

本文将详细介绍 React 中的表单处理方式,包括受控组件和非受控组件的概念、实现方法及其优缺点。通过本文,读者将能够更好地理解和使用 React 中的表单处理机制,提升开发效率和用户体验。

在 React 里,HTML 表单元素的工作方式和其他的 DOM 元素有些不同,这是因为表单元素通常会保持一些内部的 state。例如这个纯 HTML 表单只接受一个名称:

  • <form>
  • <label>
  • 名字:
  • <input type="text" name="name" />
  • </label>
  • <input type="submit" value="提交" />
  • </form>

此表单具有默认的 HTML 表单行为,即在用户提交表单后浏览到新页面。如果你在 React 中执行相同的代码,它依然有效。但大多数情况下,使用 JavaScript 函数可以很方便的处理表单的提交, 同时还可以访问用户填写的表单数据。实现这种效果的标准方式是使用“受控组件”。

1、受控组件

在 HTML 中,表单元素(如、 和 )通常自己维护 state,并根据用户输入进行更新。

而在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用setState()来更新。

我们可以把两者结合起来,使 React 的 state 成为“唯一数据源”。渲染表单的 React 组件还控制着用户输入过程中表单发生的操作-onChange。

被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”。

例如,如果我们想让前一个示例在提交时打印出名称,我们可以将表单写为受控组件:

  • import React, { Component } from 'react'
  • class App extends Component {
  • state = {
  • username: ''
  • }
  • changeUsername = (e) => {
  • this.setState({
  • username: e.target.value
  • })
  • }
  • getData (e) {
  • e.preventDefault()
  • console.log(this.state.username)
  • }
  • render () {
  • return (
  • <form onSubmit = { this.getData.bind(this) }>
  • <div>
  • <input type="text" value={ this.state.username } onChange = { this.changeUsername }/>
  • </div>
  • <input type="submit" value="提交"/>
  • </form>
  • )
  • }
  • }
  • export default App

由于在表单元素上设置了 value 属性,因此显示的值将始终为 this.state.value,这使得 React 的 state 成为唯一数据源。由于 handlechange 在每次按键时都会执行并更新 React 的 state,因此显示的值将随着用户输入而更新。

对于受控组件来说,输入的值始终由 React 的 state 驱动。你也可以将 value 传递给其他 UI 元素,或者通过其他事件处理函数重置,但这意味着你需要编写更多的代码。

2、textarea 标签

在 HTML 中, <textarea> 元素通过其子元素定义其文本:

  • <textarea>
  • 你好, 这是在 text area 里的文本
  • </textarea>

而在 React 中,<textarea> 使用 value 属性代替。这样,可以使得使用 <textarea> 的表单和使用单行 input 的表单非常类似:

  • import React, { Component } from 'react'
  • class App extends Component {
  • state = {
  • username: ''
  • }
  • changeUsername = (e) => {
  • this.setState({
  • username: e.target.value
  • })
  • }
  • getData (e) {
  • e.preventDefault()
  • console.log(this.state.username)
  • }
  • render () {
  • return (
  • <form onSubmit = { this.getData.bind(this) }>
  • <div>
  • <textarea value={ this.state.username } onChange = { this.changeUsername }></textarea>
  • </div>
  • <input type="submit" value="提交"/>
  • </form>
  • )
  • }
  • }
  • export default App

请注意,this.state.value 初始化于构造函数中,因此文本区域默认有初值。

3、select 标签

在 HTML 中,<select> 创建下拉列表标签。例如,如下 HTML 创建了水果相关的下拉列表:

  • <select>
  • <option value="grapefruit">葡萄柚</option>
  • <option value="lime">酸橙</option>
  • <option selected value="coconut">椰子</option>
  • <option value="mango">芒果</option>
  • </select>

请注意,由于 selected 属性的缘故,椰子选项默认被选中。React 并不会使用 selected 属性,而是在根 select 标签上使用 value 属性。这在受控组件中更便捷,因为您只需要在根标签中更新它。例如:

  • import React, { Component } from 'react'
  • class App extends Component {
  • state = {
  • val: ''
  • }
  • getData (e) {
  • e.preventDefault()
  • console.log(this.state.val)
  • }
  • render () {
  • return (
  • <form onSubmit = { this.getData.bind(this) }>
  • <div>
  • <select value = { this.state.val } onChange = { (e) => {
  • this.setState({
  • val: e.target.value
  • })
  • } }>
  • {/* 表达式的初始值未能匹配任何选项,
  • <select> 元素将被渲染为“未选中”状态。
  • 在 iOS 中,这会使用户无法选择第一个选项。
  • 因为这样的情况下,iOS 不会触发 change 事件。 */}
  • <option value="" disabled>请选择</option>
  • <option value="篮球">篮球</option>
  • <option value="皮球">皮球</option>
  • <option value="网球">网球</option>
  • <option value="足球">足球</option>
  • </select>
  • </div>
  • <input type="submit" value="提交"/>
  • </form>
  • )
  • }
  • }
  • export default App

总的来说,这使得, <input type="text"><textarea> 和 <select>之类的标签都非常相似—它们都接受一个 value 属性,你可以使用它来实现受控组件。

注意

你可以将数组传递到 value 属性中,以支持在 select 标签中选择多个选项:

  • <select multiple={true} value={['B', 'C']}>

参考 — 不讲解

  • class MulFlavorForm extends React.Component {
  • constructor(props) {
  • super(props);
  • this.state = {
  • value: "coconut",
  • arr: [],
  • options: [
  • { value: "grapefruit", label: "葡萄柚" },
  • { value: "lime", label: "酸橙" },
  • { value: "coconut", label: "椰子" },
  • { value: "mango", label: "芒果" }
  • ]
  • };
  • this.handleChange = this.handleChange.bind(this);
  • }
  • handleChange(e){
  • let idx = this.state.arr.findIndex(item=>{
  • return item === e.target.value
  • })
  • if (idx >= 0) {
  • this.state.arr.splice(idx,1);
  • } else {
  • this.state.arr.push(e.target.value);
  • }
  • let arr = this.state.arr;
  • this.setState({arr});
  • }
  • render() {
  • return (
  • <div>
  • <select multiple={true} value={this.state.arr} onChange={this.handleChange}>
  • {this.state.options.map((item,index) => {
  • return <option value={item.value} key={index}>{item.label}</option>;
  • })}
  • </select>
  • </div>
  • );
  • }
  • }
  • export default Test4;

4、处理多个输入

当需要处理多个 input 元素时,我们可以给每个元素添加 name 属性,并让处理函数根据 event.target.name 的值选择要执行的操作。

  • import React from 'react'
  • class App extends React.Component {
  • state = {
  • firstname: '吴',
  • lastname: '大勋'
  • }
  • handlerChange (e) {
  • console.log(e.target.name)
  • this.setState({
  • [e.target.name]: e.target.value
  • })
  • }
  • render () {
  • return (
  • <>
  • <div>
  • 姓:<input type="text" name="firstname" value={this.state.firstname} onChange = {
  • this.handlerChange.bind(this)
  • }/>
  • </div>
  • <div>
  • 名:<input type="text" name="lastname" value={this.state.lastname} onChange = {
  • this.handlerChange.bind(this)
  • }/>
  • </div>
  • <div>
  • 欢迎您: { this.state.firstname } {this.state.lastname }
  • </div>
  • </>
  • )
  • }
  • }
  • export default App

5、文件 input 标签

在 HTML 中,<input type="file"> 允许用户从存储设备中选择一个或多个文件,将其上传到服务器,或通过使用 JavaScript 的 File API (FileReader)进行控制。

  • <input type="file" />

因为它的 value 只读,所以它是 React 中的一个非受控组件。将与其他非受控组件在后续文档中一起讨论。

  • <!DOCTYPE html>
  • <html lang="en">
  • <head>
  • <meta charset="UTF-8">
  • <meta name="viewport" content="width=device-width, initial-scale=1.0">
  • <title>Document</title>
  • </head>
  • <body>
  • <input type="file" id="banner"/>
  • <button onclick="getImg()">预览图片</button>
  • <img src="" id="img" alt="">
  • </body>
  • <script>
  • function getImg(){
  • const file = document.getElementById('banner').files[0]
  • console.log(file)
  • // js 的文件api
  • const reader = new FileReader()
  • // 输出 - base64
  • reader.readAsDataURL(file)
  • reader.onload = function () {
  • document.getElementById('img').src = this.result
  • }
  • }
  • </script>
  • </html>

6、受控输入空值

在受控组件上指定 value 的 prop 会阻止用户更改输入。如果你指定了 value,但输入仍可编辑,则可能是你意外地将value 设置为 undefined 或 null。

下面的代码演示了这一点。(输入最初被锁定,但在短时间延迟后变为可编辑。)

  • // src/index.js
  • import React from 'react'
  • import ReactDOM from 'react-dom'
  • ReactDOM.render(
  • <input value="hahah" />,
  • document.getElementById('root')
  • )
  • setTimeout(() => {
  • ReactDOM.render(
  • <input value={ null } />,
  • document.getElementById('root')
  • )
  • }, 5000)

7、非受控组件

在大多数情况下,我们推荐使用 受控组件 来处理表单数据。在一个受控组件中,表单数据是由 React 组件来管理的。另一种替代方案是使用非受控组件,这时表单数据将交由 DOM 节点来处理。

要编写一个非受控组件,而不是为每个状态更新都编写数据处理函数,你可以 使用 ref 来从 DOM 节点中获取表单数据。

例如,下面的代码使用非受控组件接受一个表单的值:

  • // index.js
  • import React from 'react'
  • import ReactDOM from 'react-dom'
  • import App from './App.jsx'
  • // ReactDOM.render(
  • // <App />,
  • // document.getElementById('root')
  • // )
  • ReactDOM.render(
  • // react的严格模式
  • <React.StrictMode>
  • <App />
  • </React.StrictMode>,
  • document.getElementById('root')
  • )
  • // App.jsx - 不推荐写法 严格模式 会有警告信息
  • import React, { Component } from 'react'
  • class App extends Component {
  • getData (e) {
  • e.preventDefault()
  • console.log(this.refs.username.value)
  • }
  • render () {
  • // 不推荐这么使用,在严格模式下会爆出警告信息
  • return (
  • <form onSubmit = { this.getData.bind(this) }>
  • <div>
  • <input type="text" ref="username"/>
  • </div>
  • <input type="submit" value="提交"/>
  • </form>
  • )
  • }
  • }
  • export default App
  • // 推荐写法
  • import React, { Component } from 'react'
  • class App extends Component {
  • usernameRef = React.createRef() // 创建ref
  • getData (e) {
  • e.preventDefault()
  • // 通过this.usernameRef.current拿到DOM节点
  • console.log(this.usernameRef.current.value)
  • }
  • render () {
  • // 不推荐这么使用,在严格模式下会爆出警告信息
  • return (
  • <form onSubmit = { this.getData.bind(this) }>
  • <div>
  • <input type="text" ref={ this.usernameRef }/>
  • </div>
  • <input type="submit" value="提交"/>
  • </form>
  • )
  • }
  • }
  • export default App

因为非受控组件将真实数据储存在 DOM 节点中,所以在使用非受控组件时,有时候反而更容易同时集成 React 和非 React 代码。如果你不介意代码美观性,并且希望快速编写代码,使用非受控组件往往可以减少你的代码量。否则,你应该使用受控组件。

(1) 默认值

在 React 渲染生命周期时,表单元素上的 value 将会覆盖 DOM 节点中的值,在非受控组件中,你经常希望 React 能赋予组件一个初始值,但是不去控制后续的更新。 在这种情况下, 你可以指定一个 defaultValue 属性,而不是 value

  • import React, { Component } from 'react'
  • class App extends Component {
  • usernameRef = React.createRef() // 创建ref
  • getData (e) {
  • e.preventDefault()
  • console.log(this.usernameRef.current.value)
  • }
  • render () {
  • return (
  • <form onSubmit = { this.getData.bind(this) }>
  • <div>
  • <input type="text" defaultValue="bk2008" ref={ this.usernameRef }/>
  • </div>
  • <input type="submit" value="提交"/>
  • </form>
  • )
  • }
  • }
  • export default App

同样,<input type="checkbox"> 和 <input type="radio"> 支持 defaultChecked<select> 和 <textarea> 支持 defaultValue

(2) 文件输入

在 HTML 中,<input type="file"> 可以让用户选择一个或多个文件上传到服务器,或者通过使用 File API 进行操作。

在 React 中,<input type="file"> 始终是一个非受控组件,因为它的值只能由用户设置,而不能通过代码控制。

您应该使用 File API 与文件进行交互。下面的例子显示了如何创建一个 DOM 节点的 ref 从而在提交表单时获取文件的信息。

  • import React, { Component } from 'react'
  • class App extends Component {
  • fileRef = React.createRef()
  • imgRef = React.createRef()
  • getData () {
  • const file = this.fileRef.current.files[0]
  • console.log(file)
  • // js 的文件api
  • const reader = new FileReader()
  • // 输出 - base64
  • reader.readAsDataURL(file)
  • var that = this
  • reader.onload = function () {
  • that.imgRef.current.src = this.result
  • }
  • }
  • render () {
  • return (
  • <>
  • <input type="file" ref={ this.fileRef }/>
  • <button onClick={this.getData.bind(this)}>预览图片</button>
  • <img src="" ref={ this.imgRef } alt=""></img>
  • </>
  • )
  • }
  • }
  • export default App
方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门
本栏推荐