來源於:https://react.docschina.org/docs/forms.html
一、表單#
在 React 中,html 表單元素的工作方式和其他的 DOM 元素不太一樣,因為表單內部通常會保持一些內部的 state
,比如下面這個純 html 表單只接受一個名稱:
<form>
<label>
名字:
<input type="text" name="name" />
</label>
<input type="submit" value="提交" />
</form>
這個表單具有默認的 html 表單行為,即在用戶提交表單之後自動刷新頁面。如果你在 React 中執行相同的代碼,它仍然有效。
但大多數情況下,使用 JavaScript 函數可以很方便的處理表單的提交,同時還可以訪問用戶填寫的表單數據。實現這種效果的標準方式是使用 “受控組件”。
受控組件#
在 html 中,表單元素比如<input>
,<textarea>
,<select>
,這些表單元素通常自己維護 state
,並根據用戶輸入進行更新。而在 React 中,可變狀態(mutable state)通常保存在組件的 state 屬性中,並且只能通過使用 setState()
來更新。
我們可以把兩者結合起來,使 React 的 state 成為 “唯一數據源”。渲染表單的 React 組件還控制著用戶輸入過程中表單發生的操作。被 React 以這種方式控制取值的表單輸入元素就叫做 “受控組件”。
例如,如果我們想讓前一個示例在提交時打印出名稱,我們可以將表單寫為受控組件:
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('提交的名字: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
名字:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="提交" />
</form>
);
}
}
由於在表單元素上設置了 value
屬性,因此顯示的值將始終為 this.state.value
,這使得 React 的 state 成為唯一數據源。由於 handlechange
在每次按鍵時都會執行並更新 React 的 state,因此顯示的值將隨著用戶輸入而更新。
對於受控組件來說,輸入的值始終由 React 的 state 驅動。你也可以將 value 傳遞給其他 UI 元素,或者通過其他事件處理函數重置,但這意味著你需要編寫更多的代碼。
textarea 標籤#
在 html 中,<textarea>
元素通過其子元素定義其文本:
<textarea>
測試!
</textarea>
而在 React 中,<textarea>
使用 value
屬性代替。這樣,可以使得使用 <textarea>
的表單和使用單行 input 的表單非常類似:
class EssayForm extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '請撰寫一篇關於你喜歡的 DOM 元素的文章.'
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('提交的文章: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
文章:
<textarea value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="提交" />
</form>
);
}
}
請注意,this.state.value
初始化於構造函數中,因此文本區域默認有初值。
select 標籤#
在 HTML 中,<select>
創建下拉列表標籤。例如,如下 html 創建了水果相關的下拉列表:
<select>
<option value="grapefruit">葡萄柚</option>
<option value="lime">酸橙</option>
<option selected value="coconut">椰子</option>
<option value="mango">芒果</option>
</select>
請注意,由於 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('你喜歡的風味是: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
選擇你喜歡的風味:
<select value={this.state.value} onChange={this.handleChange}>
<option value="grapefruit">葡萄柚</option>
<option value="lime">酸橙</option>
<option value="coconut">椰子</option>
<option value="mango">芒果</option>
</select>
</label>
<input type="submit" value="提交" />
</form>
);
}
}
總的來說,這使得 <input type="text">
, <textarea>
和 <select>
之類的標籤都非常相似 — 它們都接受一個 value 屬性,你可以使用它來實現受控組件。
注意:你可以將數組傳遞到 value 屬性中,以支持在 select 標籤中選擇多個選項:
<select multiple={true} value={['B', 'C']}>
文件 input 標籤#
在 HTML 中,<input type="file">
允許用戶從存儲設備中選擇一個或多個文件,將其上傳到服務器,或通過使用 JavaScript 的 File API
進行控制。
<input type="file" />
因為它的 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.name === 'isGoing' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
render() {
return (
<form>
<label>
參與:
<input
name="isGoing"
type="checkbox"
checked={this.state.isGoing}
onChange={this.handleInputChange} />
</label>
<br />
<label>
來賓人數:
<input
name="numberOfGuests"
type="number"
value={this.state.numberOfGuests}
onChange={this.handleInputChange} />
</label>
</form>
);
}
}
這裡使用了 ES6 計算屬性名稱
的語法更新給定輸入名稱對應的 state 值:
例如:
this.setState({
[name]: value
});
等同於 es5:
var partialState = {};
partialState[name] = value;
this.setState(partialState);
另外,由於 setState()
自動將部分 state 合併到當前 state, 只需調用它更改部分 state 即可。
受控輸入空值#
在受控組件上指定 value 的 prop 會阻止用戶更改輸入。如果你指定了 value
,但輸入仍可編輯,則可能是你意外地將 value
設置為 undefined
或 null
。
下面的代碼演示了這一點。(輸入最初被鎖定,但在短時間延遲後變為可編輯。)
ReactDOM.render(<input value="hi" />, mountNode);
setTimeout(function() {
ReactDOM.render(<input value={null} />, mountNode);
}, 1000);
受控組件的替代品#
有時使用受控組件會很麻煩,因為你需要為數據變化的每種方式都編寫事件處理函數,並通過一個 React 組件傳遞所有的輸入 state。當你將之前的代碼庫轉換為 React 或將 React 應用程序與非 React 庫集成時,這可能會令人厭煩。在這些情況下,你可能希望使用非受控組件,這是實現輸入表單的另一種方式。
成熟的解決方案#
如果你想尋找包含驗證、追蹤訪問字段以及處理表單提交的完整解決方案,使用 Formik 是不錯的選擇。然而,它也是建立在受控組件和管理 state 的基礎之上 —— 所以不要忽視學習它們。