React Forms 表單處理
表單 (form) 元件透過和使用者互動的過程會產生資料狀態的變化,表單元件像是 <input>
, <textarea>
, <select>
, <option>
。
React 表單元件有一個重要的屬性 value
用來設定表單元件的值。React 還提供了 onChange
屬性用來設定 callback function 來監聽 (listen) 表單元件資料狀態的變化。當 <input>
, <textarea>
value 被改變時、當 <input>
被勾選/反勾選時、或當 <option>
被選取時,React 會執行 onChange callback 傳入一個 event object 來通知你元件的資料狀態有被改變。
受控元件 (Controlled Components)
React 稱有設定 value
屬性的表單元件叫做受控元件 (Controlled Components),受控元件的欄位內容值是使用者無法自由更動的,只能由你主動監聽 onChange
來改變 value 然後顯示回畫面,所以會叫做受控元件,通常有設定 value 的元件也會自己維護相對應的 state。
例如像下面這個例子,欄位值會固定是 Hello 不會變:
<input type="text" value="Hello" />
點我看這個例子的結果 (你可以試試看在輸入框打字)
如果要將輸入框改變的內容能反應回畫面,你需要實作 onChange
事件 callback 來將資料狀態更新回 state。例如:
class MyForm extends React.Component {
constructor(props) {
super(props);
// 初始化輸入框的 state 值為空
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
// onChange 事件處理函示
handleChange(event) {
// event.target 是當前的 DOM elment
// 從 event.target.value 取得 user 剛輸入的值
// 將 user 輸入的值更新回 state
this.setState({value: event.target.value});
}
// form onSubmit 事件處理函式
handleSubmit(event) {
alert('Submit ' + this.state.value);
event.preventDefault();
}
render() {
// 將 value 設定為 this.state.value
// 並監聽 onChange 來更新 state
return (
<form onSubmit={this.handleSubmit}>
<input type="text" value={this.state.value} onChange={this.handleChange} />
<input type="submit" value="Submit" />
</form>
);
}
}
ReactDOM.render(
<MyForm />,
document.getElementById('root')
);
非受控元件 (Uncontrolled Components)
相反的,沒有設定 value
屬性 (或將 value 設為 null value={null}
) 的表單元件叫做非受控元件 (Uncontrolled Components),也就是讓正常的 DOM 行為自己管理元素狀態,通常我們會建議避免使用非受控元件。
例如:
<input type="text" />
使用者一打什麼就會立刻反應到畫面,跟一般的 HTML DOM 一樣。
點我看這個例子的結果 (你可以試試看在輸入框打字)
非受控元件可以用 defaultValue
屬性來給定一個預設值:
<input type="text" defaultValue="haha" />
對於可以 checked 的欄位則是可以用 defaultChecked
來預設選取。
而像受控元件一樣,非受控元件也可以透過 onChange
事件監聽值的變化。
<textarea>
在 React 中,<textarea>
一樣是用 value 來設定值,不像一般 HTML textarea 的內容是放在子元素中。
像是:
<textarea value={this.state.value} onChange={this.handleChange} />
<select>
<select>
是一個下拉式的清單,在 HTML 中我們會使用 selected
在 <option>
來選定一個值:
<select>
<option value="grapefruit">Grapefruit</option>
<option value="lime">Lime</option>
<option selected value="coconut">Coconut</option>
<option value="mango">Mango</option>
</select>
然而在 React 中,一樣是統一用 value
來指定:
<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>
如果是多重選單 (multiple options) 的語法則是這樣子:
<select multiple={true} value={['B', 'C']}>
<input type="file" />
file 元件比較特別,它是一個非受控元件,因為 file 是唯讀 (read-only) 的。
我們可以透過 JavaScript 的 File API 來存取值:
class FileInput extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
// onSubmit callback
handleSubmit(event) {
event.preventDefault();
// 利用 JS File API 來操作 file 元素
// files[0].name 可以拿到檔名
alert(
`Selected file - ${this.fileInput.files[0].name}`
);
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
上傳檔案:
<input
type="file"
ref={input => {
this.fileInput = input;
}}
/>
</label>
<br />
<button type="submit">Submit</button>
</form>
);
}
}
ReactDOM.render(
<FileInput />,
document.getElementById('root')
);
上面例子中有用到 React 的 ref 語法。
處理多個 Input 欄位的技巧
通常一個表單會有很多個 input 欄位,避免寫一堆不同的 onChange callback,你也可以用同一個 callback 然後用欄位的名稱 (name) 來做分辨:
class Reservation extends React.Component {
constructor(props) {
super(props);
this.state = {
isGoing: true,
numberOfGuests: 2
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
// 從 event object 拿到 target
const target = event.target;
// 從 target.type 可以知道欄位的 type
// 分別再從 target.checked 得到選取的狀態
// 或從 target.value 取出輸入的欄位值
const value = target.type === 'checkbox' ? target.checked : target.value;
// 從 target.name 得到該欄位設定的 name
const name = target.name;
// 分別更新不同 name 欄位的 state
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>
);
}
}
ReactDOM.render(
<Reservation />,
document.getElementById('root')
);
上面例子中有用到的 [name]: value
語法是 ES6 的 computed property name。