React中的受控组件&非受控组件

2022-02-28  本文已影响0人  前端C罗

React官方的文档和社区中对于受控组件非受控组件均有相关的文章进行讲解,作为React新手老实讲还是花了些时间去理解和消化。

本文从一个新手的学习和实践的角度来试图对其进行对比,并通过hooks代码进行相应地说明。

为何有受控/非受控的区别

在HTML标签中,有一类特殊的dom标签,表单类标签,比如inputtextareaselect等。这类标签自身具备状态,并且自身会维护自己的状态。当开发者使用React来开发表单组件时,会有相应的一套状态管理机制,此时如何将React的状态和表单组件的状态统一到一起就涉及到对表单组件状态的管控。

所谓受控(controlled)与非受控(uncontrolled),其核心的区别在于组件的状态是否可以通过代码控制。顾名思义,受控是状态受代码控制,相反则是状态不受代码控制。

受控组件

下面通过简单的示例代码来展示受控组件中对于状态的管控

input组件
import {useState} from 'react'

export default function Input() {
    const [value, setValue] = useState('')
    
    const handleChange = (e: any) => {
      setValue(e.target.value)
    }
    const handleSubmit = (e: any) => {
      e.preventDefault()
      // 消费状态 value
      console.log(value)
    }
  
    return (
      <form onSubmit={handleSubmit}>
        <label>
          名字:
          <input type="text" value={value} onChange={handleChange} />
        </label>
        <input type="submit" value="提交" />
      </form>
    );
}
textarea组件

与input些许不同的是,textarea通过子元素定义其文本(状态),代码示例如下

<textarea>我是textarea的状态</textarea>

在React中,相应的状态管控则如下代码所示

import {useState} from 'react'

export default function Textarea() {
    const [value, setValue] = useState('我是textarea的状态')
    
    const handleChange = (e: any) => {
      setValue(e.target.value)
    }
    const handleSubmit = (e: any) => {
      e.preventDefault()
      // 消费状态 value
      console.log(value)
    }
  
    return (
      <form onSubmit={handleSubmit}>
        <label>
          精彩的文案:
          <textarea value={value} onChange={handleChange} />
        </label>
        <input type="submit" value="提交" />
      </form>
    );
}

从受控代码逻辑来看,input和textarea在状态管控上几乎一模一样,组件的状态均交由上层的React控制组件进行读取和设置。

select组件

先看看html中的表现,以三个颜色的列表项选择为例,简单的代码如下:

<select>
  <option value="red">红色</option>
  <option value="blue">蓝色</option>
  <option selected value="yellow">黄色</option>
</select>

select组件在options中被selected的项的value是组件的状态值,如果使用React对该状态进行控制的话,同样是修改select组件的value即可。

import {useState} from 'react'

interface SelectProps {
    list: { value: string, text: string }[]
}
export default function Select(props: SelectProps) {
    const { list } = props
    // 默认选中第2项
    const [value, setValue] = useState(list[1].value)
    
    const handleChange = (e: any) => {
      setValue(e.target.value)
    }
    const handleSubmit = (e: any) => {
      e.preventDefault()
      // 消费状态 value
      console.log(value)
    }
  
    return (
      <form onSubmit={handleSubmit}>
        <label>
          请选择颜色:
          <select onChange={handleChange} defaultValue={value}>
              {list.map(item => (<option key={item.value} value={item.value}>{item.text}</option>))}
          </select>
        </label>
        <input type="submit" value="提交" />
      </form>
    );
}

inputtextarea有个地方不同,设置初始状态时,可以通过defaultValue来触发相应的UI渲染(selected状态)。

非受控组件

从上面的介绍和示例可以看出,受控组件要求开发者接管所有与状态读取和设置逻辑,否则在状态的使用上就可能出现不一致的错误。
非受控组件的场景相应的就比较容易理解了,无外乎以下两种场景

那如何实现对非受控组件的状态使用呢?同样参考官方的案例,实现对文件选择input的封装,代码如下:

import {useRef} from 'react'

export default function FileInput() {
    const fileInputRef = useRef<HTMLInputElement>(null)
    
    const handleSubmit = (e: any) => {
      e.preventDefault()
      // 消费状态 value
      console.log(fileInputRef.current?.value)
    }
  
    return (
      <form onSubmit={handleSubmit}>
        <label>
          请选择文件:
          <input type="file" ref={fileInputRef} />
        </label>
        <input type="submit" value="提交" />
      </form>
    );
}

在上述的示例代码中,类型为file的input组件,它的状态只能由用户操作产生,不受程序代码的控制,这是非常典型的非受控组件的应用场景。
非受控组件中的关键就是通过ref保留目标组件的引用,通过组件的引用获取组件内部的状态,而组件内部的状态对外部是透明的。

小结

本文根据官方的示例,使用hooks的方式对示例代码进行重写,代码会更易于阅读理解,同时从受控组件和非受控组件各自的特点和使用场景进行相应地讲解,总结为下面几点。

上一篇下一篇

猜你喜欢

热点阅读