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

React官方的文檔和社區(qū)中對于受控組件非受控組件均有相關(guān)的文章進行講解,作為React新手老實講還是花了些時間去理解和消化。

本文從一個新手的學習和實踐的角度來試圖對其進行對比,并通過hooks代碼進行相應地說明。

為何有受控/非受控的區(qū)別

在HTML標簽中,有一類特殊的dom標簽,表單類標簽,比如inputtextarea,select等。這類標簽自身具備狀態(tài),并且自身會維護自己的狀態(tài)。當開發(fā)者使用React來開發(fā)表單組件時,會有相應的一套狀態(tài)管理機制,此時如何將React的狀態(tài)和表單組件的狀態(tài)統(tǒng)一到一起就涉及到對表單組件狀態(tài)的管控。

所謂受控(controlled)與非受控(uncontrolled),其核心的區(qū)別在于組件的狀態(tài)是否可以通過代碼控制。顧名思義,受控是狀態(tài)受代碼控制,相反則是狀態(tài)不受代碼控制。

受控組件

下面通過簡單的示例代碼來展示受控組件中對于狀態(tài)的管控

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()
      // 消費狀態(tài) 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通過子元素定義其文本(狀態(tài)),代碼示例如下

<textarea>我是textarea的狀態(tài)</textarea>

在React中,相應的狀態(tài)管控則如下代碼所示

import {useState} from 'react'

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

從受控代碼邏輯來看,input和textarea在狀態(tài)管控上幾乎一模一樣,組件的狀態(tài)均交由上層的React控制組件進行讀取和設(shè)置。

select組件

先看看html中的表現(xiàn),以三個顏色的列表項選擇為例,簡單的代碼如下:

<select>
  <option value="red">紅色</option>
  <option value="blue">藍色</option>
  <option selected value="yellow">黃色</option>
</select>

select組件在options中被selected的項的value是組件的狀態(tài)值,如果使用React對該狀態(tài)進行控制的話,同樣是修改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()
      // 消費狀態(tài) 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有個地方不同,設(shè)置初始狀態(tài)時,可以通過defaultValue來觸發(fā)相應的UI渲染(selected狀態(tài))。

非受控組件

從上面的介紹和示例可以看出,受控組件要求開發(fā)者接管所有與狀態(tài)讀取和設(shè)置邏輯,否則在狀態(tài)的使用上就可能出現(xiàn)不一致的錯誤。
非受控組件的場景相應的就比較容易理解了,無外乎以下兩種場景

  • 組件狀態(tài)外部只讀,程序無法修改
  • 組件狀態(tài)維護邏輯復雜,開發(fā)者期望組件內(nèi)部自維護狀態(tài)

那如何實現(xiàn)對非受控組件的狀態(tài)使用呢?同樣參考官方的案例,實現(xiàn)對文件選擇input的封裝,代碼如下:

import {useRef} from 'react'

export default function FileInput() {
    const fileInputRef = useRef<HTMLInputElement>(null)
    
    const handleSubmit = (e: any) => {
      e.preventDefault()
      // 消費狀態(tài) value
      console.log(fileInputRef.current?.value)
    }
  
    return (
      <form onSubmit={handleSubmit}>
        <label>
          請選擇文件:
          <input type="file" ref={fileInputRef} />
        </label>
        <input type="submit" value="提交" />
      </form>
    );
}

在上述的示例代碼中,類型為file的input組件,它的狀態(tài)只能由用戶操作產(chǎn)生,不受程序代碼的控制,這是非常典型的非受控組件的應用場景。
非受控組件中的關(guān)鍵就是通過ref保留目標組件的引用,通過組件的引用獲取組件內(nèi)部的狀態(tài),而組件內(nèi)部的狀態(tài)對外部是透明的。

小結(jié)

本文根據(jù)官方的示例,使用hooks的方式對示例代碼進行重寫,代碼會更易于閱讀理解,同時從受控組件和非受控組件各自的特點和使用場景進行相應地講解,總結(jié)為下面幾點。

  • 區(qū)別

    • 受控組件:將狀態(tài)交給組件外部管理,包括狀態(tài)的設(shè)置和使用,外部程序需要接管一切與狀態(tài)相關(guān)的邏輯,保持狀態(tài)的一致性
    • 非受控組件:在內(nèi)部自行管理狀態(tài),對外部透明,外部程序可通過組件的引用使用組件的狀態(tài)
  • 場景

    • 受控組件:使狀態(tài)在程序中保持一致性使用
    • 非受控組件
      • 不能。無法修改組件內(nèi)部狀態(tài)
      • 不想。如保持一致性的成本過高時,只通過引用使用組件狀態(tài)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容