React表单双雄:受控组件与非受控组件大对决🧐

210 阅读12分钟

React 中的表单组件 🧩

image.png

在如今的 Web 应用开发中,表单组件就像是一座桥梁🌉,连接着用户与应用程序,起着收集用户数据的关键作用。无论是日常使用的登录注册页面🔐,还是在线购物时填写收货地址📦,又或是问卷调查中的信息录入📝,表单无处不在。就拿登录注册场景来说,你在输入用户名和密码时,这些数据会被表单收集,然后传递给后端进行验证,只有验证通过✅,你才能顺利登录或注册成功。在 React 开发中,表单组件同样占据着举足轻重的地位,它是构建交互性应用的重要组成部分。而在 React 的表单世界里,受控组件和非受控组件就像是两大主角🦸‍♂️🦸‍♀️,它们各自有着独特的 “性格” 和 “技能”,今天我们就一起来深入了解一下这两位主角。


受控组件:React 的 “乖乖宝” 👶

image.png

定义

受控组件就像是 React 世界里的 “乖乖宝”,它的一切都被 React 这个 “家长” 牢牢掌控着👨‍👧‍👦。具体来说,受控组件的状态完全由 React 来管理,表单元素的值就像是孩子的行为表现,而这个行为表现是由 React 的 state(可以理解为家长的指令)来控制的。在一个登录表单中,用户名和密码输入框就是典型的受控组件🔑,它们的值被存储在 React 组件的 state 中,并且会随着用户的输入实时更新这个 state ,就像家长时刻盯着孩子的一举一动👀,孩子的行为完全符合家长的期望。


特点

  1. 状态管理:表单数据由 React 组件管理,就如同孩子的生活起居、学习等各方面都由家长精心安排和管理📚。在一个用户注册表单中,用户输入的姓名、年龄、邮箱等信息,都会被 React 组件很好地管理起来,存储在 state 中,方便后续的处理和使用。

  2. 数据绑定:使用value属性绑定状态,这就好比给孩子戴上了一个 “紧箍咒”📿,让孩子的行为始终与家长的指令保持一致。在输入框组件中,通过value={state.value}这样的方式,将输入框的值与 React 组件的 state 进行绑定,一旦 state 中的值发生变化,输入框的值也会随之改变;反之,用户在输入框中输入内容,也会触发onChange事件,进而更新 state 中的值 。

  3. 事件处理:使用onChange事件更新状态,就像是孩子每次有新的行为表现时,家长都会及时做出反应,调整对孩子的管理策略。当用户在输入框中输入内容时,onChange事件就会被触发,然后通过setState方法更新 state 中的值,从而实现对表单数据的实时跟踪和管理 。

  4. 数据流:单向数据流(从 state 到 UI),就如同家长对孩子的管理是单向的,家长发出指令,孩子按照指令行动,而不是孩子反过来影响家长。在 React 中,数据从 state 流向 UI,即 state 中的数据发生变化,会导致 UI 的更新,而 UI 的变化只能通过触发事件来更新 state,不能直接改变 state 。

  5. 实时更新:可以实时获取和显示表单值,这就像家长对孩子的情况了如指掌,随时都能知道孩子的最新状态。在受控组件中,用户输入的内容会立即反映在 UI 上,同时也能实时获取到最新的表单值,方便进行实时验证、实时计算等操作 。

  6. 表单验证:容易实现实时验证,这就好比家长可以随时检查孩子的行为是否符合要求,一旦发现不符合,就及时纠正。在受控组件中,通过onChange事件,可以很方便地对用户输入的数据进行实时验证,比如验证邮箱格式是否正确📧、密码长度是否符合要求🔒等,如果不符合要求,可以及时给出提示 。

  7. 状态同步:多个组件可以轻松同步状态,这就好比一个家庭中有多个孩子,家长可以让这些孩子的行为保持同步。在 React 应用中,如果有多个组件依赖同一个表单数据,那么通过受控组件,可以很方便地实现这些组件之间的状态同步,只要 state 中的数据发生变化,所有依赖这个数据的组件都会自动更新 。


代码示例

import { useState } from 'react'
function ControlledComponent() {
  const [value, setValue] = useState('')
  
  const handleChange = (e) => {
    setValue(e.target.value)
  }
  
  return (
    <input
      type="text"
      value={value}
      onChange={handleChange}
      placeholder="受控输入框"
    />
  )
}

在这段代码中,我们首先使用useState钩子创建了一个状态value,并初始化为空字符串。然后定义了一个handleChange函数,当输入框的值发生变化时,这个函数会被触发,它通过setValue方法将输入框的新值更新到value状态中。最后,在input标签中,通过value={value}将输入框的值与value状态进行绑定,通过onChange={handleChange}handleChange函数绑定到输入框的onChange事件上。这样,当用户在输入框中输入内容时,输入框的值会实时更新value状态,同时value状态的变化也会实时反映在输入框上,实现了输入框值与 state 的同步 。


非受控组件:DOM 的 “自由派” 🌪️

image.png

定义

与受控组件不同,非受控组件更像是一个自由随性的孩子,它的状态由 DOM 这个 “保姆” 来管理。在非受控组件中,表单元素的值就像是孩子自己的小秘密,被存储在 DOM 中,React 不会时刻盯着它。如果想要获取表单元素的值,就需要通过 ref 这个 “小助手” 来直接从 DOM 中获取 。


特点

  1. 性能:非受控组件不触发重新渲染,就好比一个安静的孩子,不会总是制造动静引起家长(React)的注意,所以性能更好⚡。在一些对性能要求较高的场景中,比如一个大数据量的表单,使用非受控组件可以减少不必要的重新渲染,提高应用的响应速度 。

  2. 简单性:代码更简洁,就像是简单直接的孩子,没有那么多复杂的心思。在实现简单表单功能时,非受控组件不需要像受控组件那样进行繁琐的状态管理和事件绑定,只需要通过 ref 获取值即可,代码量明显减少 。

  3. DOM 原生:更接近原生 HTML 表单,就像是穿着原生衣服的孩子,保留了最原始的风格。在非受控组件中,表单元素的行为和原生 HTML 表单几乎一样,开发者可以按照熟悉的原生 HTML 表单的方式来操作和使用 。

  4. 状态管理:难以实时获取表单值,这就像一个喜欢藏着自己想法的孩子,家长(React)很难随时知道他在想什么。在非受控组件中,由于表单值存储在 DOM 中,React 无法实时感知表单值的变化,只有在需要时通过 ref 去获取,这在一些需要实时处理表单值的场景中就不太方便 。

  5. 表单验证:较难实现实时验证,这就像家长很难随时检查这个自由孩子的行为是否符合要求。因为不能实时获取表单值,所以在非受控组件中实现实时验证就比较困难,通常需要在提交表单时进行一次性验证 。

  6. 状态同步:难以与其他组件同步状态,这就像一个独立的孩子,不太愿意和其他孩子(组件)一起玩耍、保持同步。由于非受控组件的状态由 DOM 管理,与 React 的 state 没有直接关联,所以在与其他依赖表单数据的组件进行状态同步时,会比较麻烦 。


代码示例

import { useRef } from 'react'
function UncontrolledComponent() {
  const inputRef = useRef(null)
  
  const handleSubmit = (e) => {
    e.preventDefault()
    console.log('表单值:', inputRef.current.value)
  }
  
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        ref={inputRef}
        defaultValue=""
        placeholder="非受控输入框"
      />
      <button type="submit">提交</button>
    </form>
  )
}

在这段代码中,我们首先使用useRef钩子创建了一个inputRef,它就像是一个指向输入框 DOM 元素的 “小指针”。然后定义了一个handleSubmit函数,当表单提交时,这个函数会被触发,它通过inputRef.current.value获取输入框的值,并打印到控制台。最后,在input标签中,通过ref={inputRef}inputRef绑定到输入框上,通过defaultValue=""设置输入框的初始值为空字符串 。


受控组件与非受控组件大对比 ⚖️

image.png

状态管理

在状态管理方面,受控组件就像是一个被严格管理的班级🏫,所有的表单数据都由 React 这个 “班主任” 统一管理,存储在 state 这个 “班级日志” 中,随时可以查看和更新 。而非受控组件则像是一个自由的社团🎉,表单数据由 DOM 这个 “社团负责人” 管理,React 想要了解表单值,就需要通过 ref 这个 “小信使” 去 DOM 那里获取 。


数据绑定

在数据绑定上,受控组件通过value属性和onChange事件这两个 “小助手” 来实现与 state 的紧密绑定 。而非受控组件的数据绑定则相对简单,它通过defaultValue属性设置初始值,就像是给一个空盒子放了一些初始物品📦,然后通过 ref 在需要时获取表单元素的值 。


实时更新与表单验证

受控组件在实时更新和表单验证方面表现出色。它可以实时获取和显示表单值,就像是一个实时播报员📢,随时告诉你表单的最新情况 。而非受控组件由于不能实时获取表单值,所以需要手动获取,就像是一个需要你主动去询问的人,只有你问他,他才会告诉你答案 。


性能与代码复杂度

从性能和代码复杂度来看,受控组件由于每次表单值的变化都会触发 state 的更新,进而导致组件的重新渲染,就像是一个爱折腾的孩子,总是制造动静,所以性能开销相对较大 。而非受控组件由于不依赖 React 的状态管理,不常触发重新渲染,就像是一个安静的孩子,不怎么制造动静,所以性能更好 。


适用场景

受控组件适用于需要实时表单验证、根据输入值动态更新其他组件、表单数据的实时处理以及复杂表单逻辑和表单状态管理的场景 。而非受控组件适用于简单的表单提交、性能要求高的场景、只需要在提交时获取表单值、与第三方库集成以及文件上传等特殊场景 。


最佳实践:两者搭配,干活不累 💼

image.png

选择建议

在实际开发中,默认情况下我们可以优先使用受控组件,因为它就像是一个贴心的小助手,提供了更好的用户体验和更可控的行为 。但是,如果遇到表单很大或者性能是关键因素的场景,这时非受控组件就像是一个高效的小能手,由于它不触发重新渲染,性能更好,所以是更好的选择 。


代码示例

// 混合使用示例
function MixedForm() {
  // 受控组件 - 需要实时验证的字段
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  
  // 非受控组件 - 简单的文件上传
  const fileRef = useRef(null)
  
  const handleSubmit = (e) => {
    e.preventDefault()
    const file = fileRef.current.files[0]
    console.log('邮箱:', email)
    console.log('密码:', password)
    console.log('文件:', file)
  }
  
  return (
    <form onSubmit={handleSubmit}>
      {/* 受控组件 */}
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="邮箱"
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="密码"
      />
      
      {/* 非受控组件 */}
      <input
        type="file"
        ref={fileRef}
      />
      
      <button type="submit">提交</button>
    </form>
  )
}

在这段代码中,我们创建了一个MixedForm组件,展示了如何在同一个表单中混合使用受控组件和非受控组件 。


总结与展望 🚀

image.png

在 React 的表单世界里,受控组件和非受控组件各有千秋,就像是两个身怀绝技的武林高手,在不同的场景中发挥着独特的作用 。受控组件凭借其强大的状态管理能力、实时更新和表单验证功能,成为处理复杂表单逻辑和需要实时交互场景的首选 。而非受控组件则以其简洁的代码和良好的性能,在简单表单提交和性能要求高的场景中展现出优势 。

在实际开发中,我们就像是经验丰富的指挥官,要根据具体的需求和场景,灵活地选择使用受控组件和非受控组件,让它们发挥出最大的价值 。有时,我们还可以让它们携手合作,就像一个默契的团队,共同打造出功能强大、性能优越的表单 。

在未来的 React 开发中,随着技术的不断发展和应用场景的日益复杂,受控组件和非受控组件也将不断进化和完善,为我们提供更多、更好的选择 。希望大家在今后的 React 开发之旅中,能够熟练地驾驭这两位 “武林高手”,开发出更加优秀的 Web 应用 !✨