表單元素是一類擁有內部狀態的元素,這些狀態由其自身維護,通過這類元素可讓用戶與Web應用進行交互。HTML中的表單元素(例如\<input>、\<select>和\<radio>等)在React中都有相應的組件實現,不僅如此,React還將它們分成兩種:受控組件和非受控組件。
## 一、受控組件
  受控組件(Controlled Component)是指那些受React控制的表單元素,其狀態(value、checked等屬性)的變更由組件的state管理。對于不同的表單元素,其受控組件的形式會有所差異,接下來會講解其中的三類。
**1)文本框**
  常用的單行文本框是一個type屬性為“text”的\<input>元素,它的值(即狀態)由value屬性控制。如果要監聽文本框的狀態變化,那么可以像下面這樣操作。
~~~js
class Text extends React.Component {
constructor(props) {
super(props);
this.state = {value: "init"};
this.handle = this.handle.bind(this);
}
handle(e) {
this.setState({value: e.target.value.toUpperCase()});
}
render() {
return <input value={this.state.value} onChange={this.handle} type="text" />;
}
}
~~~
  上述代碼實現了一個簡單的功能,在改變文本框中的內容時,自動將其轉換成大寫字母。具體的更新過程可分為四步:
  (1)在構造函數中初始化組件的state,并為文本框設置默認值。
  (2)文本框注冊onChange事件,監聽其值的變化。
  (3)在事件處理程序handle()中,通過e.target.value讀取到輸入的值,修改并同步(調用this.setState()方法)到組件的state中。
  (4)組件重新渲染,完成文本框的內容更新。
  其他兩類受控組件的更新過程與之類似,只是在細節處理上有所不同。
  觀察上面的示例可以發現,文本框的數據來源于組件的state,通過onChange事件將輸入的新數據再同步給組件的state,從而完成了一次雙向數據綁定。
  React中的\<textarea>元素(多行文本框),其使用類似于上面的\<input>元素,也是通過value屬性來獲取值的,如下代碼所示,省略了構造函數和事件處理程序。
~~~js
class TextArea extends React.Component {
render() {
return <textarea value={this.state.value} onChange={this.handle} />;
}
}
~~~
  而HTML中的\<textarea>元素則會將值定義成子元素,并且包含結束標簽,如下所示。
~~~html
<textarea>init</textarea>
~~~
**2)單選框和復選框**
  單選框是一個type屬性為“radio”的\<input>元素,復選框是一個type屬性為“checkbox”的\<input>元素。與之前的文本框不同,React控制的不是它們的值,而是選中狀態,即布爾屬性checked。在下面的例子中,監聽了每個單選框的checked屬性。
~~~js
class Radio extends React.Component {
constructor(props) {
super(props);
this.state = { gender: "" };
this.handle = this.handle.bind(this);
}
handle(e) {
this.setState({
gender: e.target.value
});
}
render() {
return (
<>
<input name="gender" value="1" onChange={this.handle} type="radio"
checked={this.state.gender == "1"}
/>男
<input name="gender" value="2" onChange={this.handle} type="radio"
checked={this.state.gender == "2"}
/>女
</>
);
}
}
~~~
  復選框能選中多個項,其操作要比單選框繁瑣許多。在下面的例子中,不但監聽了每個復選框的checked屬性,還將處于選中狀態的值提取了出來,組成一個數組。
~~~js
class Checkbox extends React.Component {
constructor(props) {
super(props);
this.state = { colors: [] }; //保存復選框值的數組
this.handle = this.handle.bind(this);
}
handle(e) {
const { checked, value } = e.target;
let { colors } = this.state;
if (checked && colors.indexOf(value) == -1) {
colors.push(value); //已選中并且數組中未有該值,就在末尾插入
} else {
colors = colors.filter(item => item != value); //未選中,就將該值過濾掉
}
this.setState({ colors });
}
render() {
return (
<>
<input name="colors" value="1" onChange={this.handle} type="checkbox"
checked={this.state.colors.indexOf("1") >= 0}
/>紅
<input name="colors" value="2" onChange={this.handle} type="checkbox"
checked={this.state.colors.indexOf("2") >= 0}
/>綠
<input name="colors" value="3" onChange={this.handle} type="checkbox"
checked={this.state.colors.indexOf("3") >= 0}
/>藍
</>
);
}
}
~~~
  雖然React處理單選框和復選框的方式要比在HTML中復雜一點,但是保證了組件的state是元素狀態的唯一來源,進而讓更新過程更加可靠和可控。
**3)選擇框**
  在HTML中,\<select>元素(選擇框)會包含多個用來表示選項的\<option>元素,而選中的項會被定義一個selected屬性,如下代碼所示,第二個\<option>元素處于選中狀態。
~~~html
<select>
<option value="1">strick</option>
<option value="2" selected>freedom</option>
<option value="3">jane</option>
</select>
~~~
  在React中,只需對\<select>元素定義value屬性就能決定當前的選中項,如下代碼所示,這比用DOM的方式操作選項要簡潔得多。
~~~js
class Select extends React.Component {
constructor(props) {
super(props);
this.state = { value: "" };
this.handle = this.handle.bind(this);
}
handle(e) {
this.setState({ value: e.target.value });
}
render() {
return (
<select value={this.state.value} onChange={this.handle}>
<option value="1">strick</option>
<option value="2">freedom</option>
<option value="3">jane</option>
</select>
);
}
}
~~~
  只要給\<select>元素添加multiple屬性并將其賦為true就能變為多選,如下代碼所示,此時傳給value屬性的是一個數組。
~~~js
class MulSelect extends React.Component {
constructor(props) {
super(props);
this.state = { values: [] };
this.handle = this.handle.bind(this);
}
handle(e) {
const { options } = e.target; //options是一個類數組對象
const values = Object.keys(options) //將options的索引組成一個數組
.filter(i => options[i].selected) //過濾出選中項
.map(i => options[i].value); //提取選中項組成新數組
this.setState({ values });
}
render() {
return (
<select value={this.state.values} onChange={this.handle} multiple={true}>
<option value="1">strick</option>
<option value="2">freedom</option>
<option value="3">jane</option>
</select>
);
}
}
~~~
## 二、非受控組件
  非受控組件(Uncontrolled Component)的定義正好與受控組件的相左,其狀態由自己管理,通常使用ref屬性([第5篇](https://www.cnblogs.com/strick/p/10593862.html)中講解過)獲取表單元素的值。在下面的示例中,文本框在失去焦點時,能自動將其內容轉換成大寫字母。如果用受控組件的形式完成相同的功能,那么會較為繁瑣。
~~~js
class Text extends React.Component {
constructor(props) {
super(props);
this.handle = this.handle.bind(this);
}
handle() {
this.input.value = this.input.value.toUpperCase();
}
render() {
return <input onBlur={this.handle} type="text" ref={ input => {this.input = input}}/>;
}
}
~~~
  在render()方法中,首先為文本框注冊onBlur事件,然后定義ref屬性,其值是一個回調函數。當組件被掛載時,就會執行該回調函數,然后就能讓this.input指向一個文本框,從而在事件處理程序handle()中就能通過this.input讀取到文本框中的內容。
  在React中,有一個表單元素比較特殊,那就是上傳按鈕。它只有非受控組件的形式,因為其值只能由用戶傳入,不能被組件的state所控制。
**1)默認值**
  如果要指定非受控組件的默認值,那么可通過定義defaultValue或defaultChecked屬性實現,前者適用于文本框、選擇框等元素,后者適用于單選框和復選框。下面的示例分別給文本框和單選框設置了默認值,為了便于觀察,只放出了關鍵代碼。
~~~js
class Text extends React.Component {
render() {
return <input type="text" defaultValue="init"/>;
}
}
class Radio extends React.Component {
render() {
return (
<>
<input name="gender" value="1" type="radio"/>男
<input name="gender" value="2" type="radio" defaultChecked={true}/>女
</>
);
}
}
~~~
*****
> 原文出處:
[博客園-React躬行記](https://www.cnblogs.com/strick/category/1455720.html)
[知乎專欄-React躬行記](https://zhuanlan.zhihu.com/pwreact)
已建立一個微信前端交流群,如要進群,請先加微信號freedom20180706或掃描下面的二維碼,請求中需注明“看云加群”,在通過請求后就會把你拉進來。還搜集整理了一套[面試資料](https://github.com/pwstrick/daily),歡迎瀏覽。

推薦一款前端監控腳本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不僅能監控前端的錯誤、通信、打印等行為,還能計算各類性能參數,包括 FMP、LCP、FP 等。
- ES6
- 1、let和const
- 2、擴展運算符和剩余參數
- 3、解構
- 4、模板字面量
- 5、對象字面量的擴展
- 6、Symbol
- 7、代碼模塊化
- 8、數字
- 9、字符串
- 10、正則表達式
- 11、對象
- 12、數組
- 13、類型化數組
- 14、函數
- 15、箭頭函數和尾調用優化
- 16、Set
- 17、Map
- 18、迭代器
- 19、生成器
- 20、類
- 21、類的繼承
- 22、Promise
- 23、Promise的靜態方法和應用
- 24、代理和反射
- HTML
- 1、SVG
- 2、WebRTC基礎實踐
- 3、WebRTC視頻通話
- 4、Web音視頻基礎
- CSS進階
- 1、CSS基礎拾遺
- 2、偽類和偽元素
- 3、CSS屬性拾遺
- 4、浮動形狀
- 5、漸變
- 6、濾鏡
- 7、合成
- 8、裁剪和遮罩
- 9、網格布局
- 10、CSS方法論
- 11、管理后臺響應式改造
- React
- 1、函數式編程
- 2、JSX
- 3、組件
- 4、生命周期
- 5、React和DOM
- 6、事件
- 7、表單
- 8、樣式
- 9、組件通信
- 10、高階組件
- 11、Redux基礎
- 12、Redux中間件
- 13、React Router
- 14、測試框架
- 15、React Hooks
- 16、React源碼分析
- 利器
- 1、npm
- 2、Babel
- 3、webpack基礎
- 4、webpack進階
- 5、Git
- 6、Fiddler
- 7、自制腳手架
- 8、VSCode插件研發
- 9、WebView中的頁面調試方法
- Vue.js
- 1、數據綁定
- 2、指令
- 3、樣式和表單
- 4、組件
- 5、組件通信
- 6、內容分發
- 7、渲染函數和JSX
- 8、Vue Router
- 9、Vuex
- TypeScript
- 1、數據類型
- 2、接口
- 3、類
- 4、泛型
- 5、類型兼容性
- 6、高級類型
- 7、命名空間
- 8、裝飾器
- Node.js
- 1、Buffer、流和EventEmitter
- 2、文件系統和網絡
- 3、命令行工具
- 4、自建前端監控系統
- 5、定時任務的調試
- 6、自制短鏈系統
- 7、定時任務的進化史
- 8、通用接口
- 9、微前端實踐
- 10、接口日志查詢
- 11、E2E測試
- 12、BFF
- 13、MySQL歸檔
- 14、壓力測試
- 15、活動規則引擎
- 16、活動配置化
- 17、UmiJS版本升級
- 18、半吊子的可視化搭建系統
- 19、KOA源碼分析(上)
- 20、KOA源碼分析(下)
- 21、花10分鐘入門Node.js
- 22、Node環境升級日志
- 23、Worker threads
- 24、低代碼
- 25、Web自動化測試
- 26、接口攔截和頁面回放實驗
- 27、接口管理
- 28、Cypress自動化測試實踐
- 29、基于Electron的開播助手
- Node.js精進
- 1、模塊化
- 2、異步編程
- 3、流
- 4、事件觸發器
- 5、HTTP
- 6、文件
- 7、日志
- 8、錯誤處理
- 9、性能監控(上)
- 10、性能監控(下)
- 11、Socket.IO
- 12、ElasticSearch
- 監控系統
- 1、SDK
- 2、存儲和分析
- 3、性能監控
- 4、內存泄漏
- 5、小程序
- 6、較長的白屏時間
- 7、頁面奔潰
- 8、shin-monitor源碼分析
- 前端性能精進
- 1、優化方法論之測量
- 2、優化方法論之分析
- 3、瀏覽器之圖像
- 4、瀏覽器之呈現
- 5、瀏覽器之JavaScript
- 6、網絡
- 7、構建
- 前端體驗優化
- 1、概述
- 2、基建
- 3、后端
- 4、數據
- 5、后臺
- Web優化
- 1、CSS優化
- 2、JavaScript優化
- 3、圖像和網絡
- 4、用戶體驗和工具
- 5、網站優化
- 6、優化閉環實踐
- 數據結構與算法
- 1、鏈表
- 2、棧、隊列、散列表和位運算
- 3、二叉樹
- 4、二分查找
- 5、回溯算法
- 6、貪心算法
- 7、分治算法
- 8、動態規劃
- 程序員之路
- 大學
- 2011年
- 2012年
- 2013年
- 2014年
- 項目反思
- 前端基礎學習分享
- 2015年
- 再一次項目反思
- 然并卵
- PC網站CSS分享
- 2016年
- 制造自己的榫卯
- PrimusUI
- 2017年
- 工匠精神
- 2018年
- 2019年
- 前端學習之路分享
- 2020年
- 2021年
- 2022年
- 2023年
- 2024年
- 日志
- 2020