react學習筆記 item8 --- 表單
表單是應用必不可少的一部分,只要需要使用者輸入,哪怕是最簡單的輸入,都離不開表單。一直以來,單頁應用中的表單都很難處理好,因為表單中充斥著使用者變化莫惻的狀態。在 react 中,表單的處理也與原生的 JS 處理方法稍有不同。在 react 中,表單元件有兩種型別:受限元件(約束元件)和 不受限元件(無約束元件)。
受限元件
約束元件的模式與React 其他型別元件的模式一致。表單元件的狀態交由React 元件控制,狀態值被儲存在React 元件的state 中。如果想要更好地控制表單元件,推薦使用約束元件。
先看下面一個例子
// 示例1
var MyForm = React.createClass({
render:function (){
return(
<input type='text' value='defaultText' />
);
}
});
ReactDOM.render(
<MyForm />,
document.getElementById('example')
);
上面的程式碼將渲染出一個值為 defaultText
的 input 元素。使用者在渲染出來的元素裡輸入任何值都不起作用,因為 React 已經賦值為 defaultText
。通過執行,我們在 chrome 瀏覽器中檢視到如下結果:
在控制檯打印出了一條 warning, 提醒我們設定了 value 的時候需要同時提供 onChange 事件,不然表單是隻讀的。因此如果想響應更新使用者輸入的值,就得使用 onChange 事件:
// 示例2
var MyForm = React.createClass({
getInitialState: function() {
return {inputValue: 'defaultText'};
},
handleInputChange:function(e){
this.setState({
inputValue: e.target.value
});
},
render:function (){
return(
<input type='text' value={this.state.inputValue} onChange={this.handleInputChange} />
);
}
});
ReactDOM.render(
<MyForm />,
document.getElementById('example')
);
示例 2 與示例 1 相比,最顯著的變化就是<input/>
的值儲存在父元件的 state 中,然後通過 onChange 事件去改變 state 。以下是示例2 的過程:
- getlnitialState 設定defaultValue 。
- < input/> 的值在渲染時被設定。
- < input/ > 的值onChange 時, change 處理器被呼叫。
- change 處理器更新state 。
- 在重新撞染時更新< input/> 的值。
雖然與無約束元件相比,程式碼量增加了不少,但是現在可以控制資料流,在使用者輸入資料時更新state 。
在示例2 的基礎上,我們改變handleInputChange
,實現當用戶輸入時,把字元都轉換成大寫:
handleInputChange:function(e){
this.setState({
inputValue: e.target.value.toUpperCase()
});
},
你可能會注意到,在使用者輸入資料後,小寫字元轉成大寫形式並新增到輸入框時,並不會發生閃爍。這是因為React 攔截了瀏覽器原生的change 事件,在setState 被呼叫後, 這個元件就會重新渲染輸入框。然後React 計算差異,更新輸入框的值。
不受限元件
沒有設定 value(或者設為 null) 的 <input>
元件是一個不受限元件。對於不受限的 <input>
元件,渲染出來的元素直接反應使用者輸入。不受限元件的最大的特點就是表單元件的值是不受React 元件控制的,而是由<input>
自己控制。
// 示例3
var MyForm = React.createClass({
render:function(){
return(
<input type='text' defaultValue='defaultText' />
);
}
});
ReactDOM.render(
<MyForm />,
document.getElementById('example')
);
我們可以通過defaultValue 屬性設定< input/ >
的預設值。元件的value 井非由父元件設定, 而是讓<input/>
自己控制自己的值。
對於一個無約束的元件,如果要訪問DOM 節點的值,需要給<input/>
新增一個 ref 屬性。ref 是一個不屬於DOM 屬性的特殊屬性,用來標記DOM 節點,可以通過this 上下文訪問這個節點。為了便於訪問,元件中所有的ref 都新增到了this.refs 上。
// 示例4
var MyForm = React.createClass({
submitHandler:function(e){
e.preventDefault();
var value = ReactDOM.findDOMNode(this.refs.myInput).value;
alert(value);
},
render:function(){
return(
<form onSubmit={this.submitHandler}>
<input ref="myInput" type='text' defaultValue='defaultText' />
<button type='submit'>提交</button>
</form>
);
}
});
ReactDOM.render(
<MyForm />,
document.getElementById('example')
);
在示例4 中,我們設定了<input />
的 ref ,然後通過this.refs.myInput 獲取到該節點,這樣就能獲取的節點的值了。
這裡需要注意一下版本相容性問題:
當使用時this.refs.myInput.getDOMNode()
,時如果使用 react 版本是 0.14.8及以前的版本,可以正常執行,但是如果使用的是0.15.0後的版本時,可能會報錯:
this.refs.myInput.getDOMNode is not a function(…)
這是需要用到 ReactDOM.findDOMNode(this.refs.myInput);
。如果出現了相關的報錯,可以先列印一下this.refs.myInput
,檢視是否找到相應的節點,然後再檢視不同版本對應的方法。
常用表單元素
除了上面<input />
,下面介紹一些表單中常用的其他表單元素。
< label/ >
Label 是表單元素中很重要的元件,通過Label 可以明確地向用戶傳達你的要求,提升單選框和複選框的可用性。但是在使用Label 時需要注意 for 屬性,在React 中,與class 變成了className 類似, for 也變成了htmlFor :
<label htmlFor="name">Name:</label>
< textarea/ >
對 HTML 而言,讓開發者設定多行的值很容易。但是,React 是 JavaScript,沒有字元限制,可以使用 \n 實現換行。<textarea />
被改得更像<input />
了,允許我們設定value 和defaultValue 。
//非約束的
<textarea defaultValue="Hello World" />
// 約束的
<textarea value={this.state.textareaValue} onChange={this.handleChange} />
< select/ >
HTML 中<select>
通常使用 <option>
的 selected 屬性設定選中狀態;React 為了更方面的控制組件,<select />
現在接受 value 和defaultValue 來設定已選項,我們可以更容易地對它的值進行操作。
//非約束的
<select defaultValue="B">
<option value="A">First Option</option>
<option value="B">Second Option</option>
<option value="C">Third Option</option>
</select>
// 約束的
<select value={this.state . helloTo} onChange={this.handleChange}>
<option value="A">First Option</option>
<option value="B 與Second Option</option>
<option value="C">Third Option</option>
</select>
< checkbox / >
複選框和單選框使用的則是另外一種完全不同的控制方式。在HTML 中,型別為checkbox 或 radio 的 < input/ >
與型別為text 的< input/>
的行為完全不一樣。通常,複選框或者單選框的值是不變的,只有checked 狀態會變化。要控制複選框或者單選框,就要控制它們的checked 屬性。
我們先看一個簡單的示例:
// 示例5
var MyForm = React.createClass({
getInitialState: function() {
return {
checked: true
};
},
handleChange: function(event) {
this.setState({
checked: event.target.checked
});
},
submitHandler: function(event) {
event.preventDefault();
alert(this.state.checked);
},
render: function() {
return (
<form onSubmit={this.submitHandler}>
<input type="checkbox" value="A" checked={this.state.checked} onChange={this.handleChange} />
<br/>
<button type="submit">提交</button>
</form>
);
}
});
上述<input/ >
的值一直都是A ,只有checked 的狀態在變化。我們把 checked 的狀態儲存在 state 中,通過 state 的更新去渲染頁面效果。
一個簡單的表單示例
// 示例6
var MyForm = React.createClass({
getInitialState:function(){
return {
inputValue: 'input value',
selectValue: 'A',
radioValue:'',
checkValues:[],
textareaValue:'some text here,,,,,'
}
},
handleSubmit:function(e){
e.preventDefault();
var formData = {
input: ReactDOM.findDOMNode(this.refs.goodInput).value,
select: ReactDOM.findDOMNode(this.refs.goodSelect).value,
textarea: ReactDOM.findDOMNode(this.refs.goodTextarea).value,
radio: this.state.radioValue,
check: this.state.checkValues,
}
console.log('the form result is:')
console.log(formData);
this.refs.RadioButtons.saySomething();
},
handleRadio:function(e){
this.setState({
radioValue: e.target.value,
})
},
handleCheck:function(e){
var checkValues = this.state.checkValues.slice();
var newVal = e.target.value;
var index = checkValues.indexOf(newVal);
if( index == -1 ){
checkValues.push( newVal )
}else{
checkValues.splice(index,1);
}
this.setState({
checkValues: checkValues,
})
},
render: function(){
return (
<form onSubmit={this.handleSubmit}>
<input ref="goodInput" type="text" defaultValue={this.state.inputValue }/>
<br/>
選項:
<select defaultValue={ this.state.selectValue } ref="goodSelect">
<option value="A">A</option>
<option value="B">B</option>
<option value="C">C</option>
<option value="D">D</option>
<option value="E">E</option>
</select>
<br/>
<p>radio button!</p>
<RadioButtons ref="RadioButtons" handleRadio={this.handleRadio} />
<br/>
<Checkboxes handleCheck={this.handleCheck} />
<br/>
<textarea defaultValue={this.state.textareaValue} ref="goodTextarea"></textarea>
<button type="submit">提交</button>
</form>
)
}
});
var RadioButtons = React.createClass({
saySomething:function(){
alert("RadioButtons saying....");
},
render:function(){
return (
<span>
A
<input onChange={this.props.handleRadio} name="goodRadio" type="radio" value="A"/>
B
<input onChange={this.props.handleRadio} name="goodRadio" type="radio" value="B"/>
C
<input onChange={this.props.handleRadio} name="goodRadio" type="radio" value="C"/>
</span>
)
}
});
var Checkboxes = React.createClass({
render: function(){
return (
<span>
A
<input onChange={this.props.handleCheck} name="goodCheckbox" type="checkbox" value="A"/>
B
<input onChange={this.props.handleCheck} name="goodCheckbox" type="checkbox" value="B" />
C
<input onChange={this.props.handleCheck} name="goodCheckbox" type="checkbox" value="C" />
</span>
)
}
})
示例6 中將<radio/>
和<checkbox/>
封裝成了獨立的元件,提交表單時,利用this.refs 獲取對應的表單元素的值。這樣就不需要為每一個元件寫一個獲取元件value 的函數了。渲染結果如下: