HTML表單元素和 React里的其他DOM元素有些不同,因為它們會保留一些內部的狀態。舉個例子,這個普通的表單接受唯一的name值:
<form>
<label>
Name:
<input type="text" name="name" />
</label>
<input type="submit" value="Submit" />
</form>
這個表單具有默認的表單行為,當用戶提交表單就會跳轉到新頁面。如果你想要在React里實現此行為,它自然而然就會實現。但是在大多數情況下,定義一個控制表單提交並且有能力控制用戶輸入的表單數據的js函數會更方便。實現這個的標准方法是一種叫“受控組件”的技術。
受控組件
在HTML里,<input>,<textarea>和<select>這類元素通常包含它們自己的狀態並且會基於用戶輸入而更新狀態。在React中,易變的狀態通常會保存在組件的state屬性里,並且只能使用setState()來更新。
我們可以聯合兩種狀態通過設置React state為“單一數據源”。然后React組件會渲染表單也會控制隨后用戶的輸入。這樣子的表單元素輸入的值被React控制的方法叫做“受控組件”。
舉個例子,如果我們想要讓上一個例子當提交的時候記錄name值,我們可以把表單寫成一個受控組件:
class NameForm extends React.Component { constructor(props) { super(props); this.state = {value: ''}; this.handleChange = this.handleChange.bind(this); this.handleSubmit = this.handleSubmit.bind(this); } handleChange(event) { this.setState({value: event.target.value}); } handleSubmit(event) { alert('A name was submitted: ' + this.state.value); event.preventDefault(); } render() { return ( <form onSubmit={this.handleSubmit}> <label> Name: <input type="text" value={this.state.value} onChange={this.handleChange} /> </label> <input type="submit" value="Submit" /> </form> ); } }
只要value屬性被設置在表單元素上,被顯示的value值就永遠會是this.state.value,使得React的state是單一數據源。當每次修改表單值的時候handleChange就會更新React的state,只要輸入改變顯示的值就會更新。
通過控制組件,每一個state的變化都會有一個聯合的處理函數。這讓它可以明確的修改或者確認用戶輸入。舉個例子,如果我們想要強制name值都用大寫字母來寫,那么我們應該像這樣寫handleChange函數:
handleChange(event) { this.setState({value: event.target.value.toUpperCase()}); }
textarea標簽
在HTML里,一個<textarea>元素通過了子元素定義了它的文本:
<textarea> Hello there, this is some text in a text area </textarea>
在React里,一個<textarea>使用value屬性來代替文本值。像這樣,一個表單使用一個<textarea>能夠像這樣寫,跟使用單行input的表單類似:
class EssayForm extends React.Component { constructor(props) { super(props); this.state = { value: 'Please write an essay about your favorite DOM element.' }; this.handleChange = this.handleChange.bind(this); this.handleSubmit = this.handleSubmit.bind(this); } handleChange(event) { this.setState({value: event.target.value}); } handleSubmit(event) { alert('An essay was submitted: ' + this.state.value); event.preventDefault(); } render() { return ( <form onSubmit={this.handleSubmit}> <label> Name: <textarea value={this.state.value} onChange={this.handleChange} /> </label> <input type="submit" value="Submit" /> </form> ); } }
注意this.state.value在構造函數里被初始化,因此文本區域一開始就會有文本值。
select標簽
在HTML里,<select>創建了一個下拉列表。舉個例子,這段HTML創建了一個香料的下拉列表:
<select> <option value="grapefruit">Grapefruit</option> <option value="lime">Lime</option> <option selected value="coconut">Coconut</option> <option value="mango">Mango</option> </select>
注意Coconut選項是初始就被選擇的,因為selected屬性。在React里,我們不使用selected屬性,而是在select標簽里使用value屬性。在控制組件里這樣更加方便因為你只需要在一個地方更新默認選擇項即可。舉個例子:
class FlavorForm extends React.Component { constructor(props) { super(props); this.state = {value: 'coconut'}; this.handleChange = this.handleChange.bind(this); this.handleSubmit = this.handleSubmit.bind(this); } handleChange(event) { this.setState({value: event.target.value}); } handleSubmit(event) { alert('Your favorite flavor is: ' + this.state.value); event.preventDefault(); } render() { return ( <form onSubmit={this.handleSubmit}> <label> Pick your favorite La Croix flavor: <select value={this.state.value} onChange={this.handleChange}> <option value="grapefruit">Grapefruit</option> <option value="lime">Lime</option> <option value="coconut">Coconut</option> <option value="mango">Mango</option> </select> </label> <input type="submit" value="Submit" /> </form> ); } }
總得來說,<input type="text">,<textarea>和<select>都類似,他們都接受一個value屬性來完成一個控制組件。
file input 標簽
在HTML當中,<input type="file"> 允許用戶從他們的存儲設備中選擇一個或多個文件以提交表單的方式上傳到服務器上, 或者通過 Javascript 的 File API 對文件進行操作 。
由於該標簽的 value 屬性是只讀的, 所以它是 React 中的一個非受控組件。我們會把它和其他非受控組件一起在后面的章節進行詳細的介紹。
處理多個輸入
當你需要處理多個input表單元素的時候,你可以給每一個元素添加一個name屬性並且讓處理函數選擇基於event.target.name的值。
舉個例子:
class Reservation extends React.Component { constructor(props) { super(props); this.state = { isGoing: true, numberOfGuests: 2 }; this.handleInputChange = this.handleInputChange.bind(this); } handleInputChange(event) { const target = event.target; const value = target.type === 'checkbox' ? target.checked : target.value; const name = target.name; this.setState({ [name]: value }); } render() { return ( <form> <label> Is going: <input name="isGoing" type="checkbox" checked={this.state.isGoing} onChange={this.handleInputChange} /> </label> <br /> <label> Number of guests: <input name="numberOfGuests" type="number" value={this.state.numberOfGuests} onChange={this.handleInputChange} /> </label> </form> ); } }
注意我們是怎樣使用ES6的“計算出的屬性名”語法來更新state key通過對應的input name:
this.setState({ [name]: value });
ES5語法這樣寫:
var partialState = {}; partialState[name] = value; this.setState(partialState);
同樣,只要setState()自動的將部分state合並到了當前的state,我們只需要調用它來更新被改變的部分。
控制組件的可選方案
有時使用受控組件可能很繁瑣,因為您要為數據可能發生變化的每一種方式都編寫一個事件處理程序,並通過一個組件來管理全部的狀態。當您將預先存在的代碼庫轉換為React或將React應用程序與非React庫集成時,這可能變得特別煩人。在以上情況下,你或許應該看看非受控組件,這是一種表單的替代技術。