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

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的方式对示例代码进行重写,代码会更易于阅读理解,同时从受控组件和非受控组件各自的特点和使用场景进行相应地讲解,总结为下面几点。

  • 区别

    • 受控组件:将状态交给组件外部管理,包括状态的设置和使用,外部程序需要接管一切与状态相关的逻辑,保持状态的一致性
    • 非受控组件:在内部自行管理状态,对外部透明,外部程序可通过组件的引用使用组件的状态
  • 场景

    • 受控组件:使状态在程序中保持一致性使用
    • 非受控组件
      • 不能。无法修改组件内部状态
      • 不想。如保持一致性的成本过高时,只通过引用使用组件状态
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容