不太清楚有多少初學React的同學和博主當時一樣,在看完React的生命周期、數據流之后覺得已經上手了,甩開文檔啪啪啪的開始敲了起來。結果...居然被一個input標簽給教做人了。
故事是這樣的:首先你創建了一個input標簽
var React = require('react'), ReactDOM = require('react-dom'); var Test = React.render(function() { render: function() { return (<input type="text" />); } }); ReactDOM.render(<Text />, document.querySelector('#container'));
一切都是如此的輕松自然,接着由於需求你給input上設置了一個默認值:
<input value='123' type='text' />
突然你發現,唉我擦!輸入框里的值不能改動了,刪也刪不掉。你以為電腦卡死了,刷新了幾遍還是這樣。然而把value刪除就復原了,你不得不又返回去看文檔。
原理:在React中表單組件分為約束組件和無約束組件兩種。
- 無約束組件,是指其value值不通過的props或者state來設置,僅由其自身來決定。表單組件的值的變化也不會被記錄,只能通過找到DOM節點的方式來獲取。
- 約束組件,是React中推薦的表單的使用方式。表單組件的值並不是由其自身決定,而是通過父組件傳遞或者本身的state來控制。其內容的每次變化都會被保存,需要時僅需要通過this.state便能獲取。
約束狀態的input組件寫法如下:
var Test = Rreact.createClass({ getInitialState: function() { return {value: ''}; }, render: function({ return (<input type='text' value={this.state.value} onChange={this.handlerChange} />); }), handlerChange: function(event) { var newValue = event.target.value; this.setState({value: newValue}); } });
上例中,我們監聽了input的onchange事件,每一次內容的更改實際上是更改組件的state屬性,通過state的變化來觸發DOM元素的變化。
React之所以這么做的原因,是因為React其實為一個狀態機,頁面上所有的DOM元素的狀態都需要被其所知所控制。
在繼續理解表單組件之前,組件的state是必須被開發者所理解的。通常很多人喜歡將state與props一起講解,這里博主認為通過state在表單組件的實際應用講解可能更加直觀。
State
每一本介紹React的書或文檔都會把state和props放在一起詳細的比較,其實最簡單的說:state是組件內部用來控制組件狀態的屬性,props是組件之間用來通信的屬性。
創建
state是通過名為getInitialState的生命周期函數創建的,其return出一個對象作為state值。如果你申明了該函數卻沒有返回值是會報錯的。
創建之后,在組件內部的所有函數都可以用 this.state.屬性名 來訪問該屬性。
修改
state的值並不是固定的,開發都通過在合適的時機改變它從而達到改變頁面展示的目的。
改變state的唯一是this.setState,該方法可以說是整個React系統的"扳機",正常情況(除了直接操作DOM)下所有的頁面更新都是由這個方法來觸發的。
var AddNum = React.createClass({ getInitialState: function() { return {number: 0}; }, render:function() { return (<span onClick={this.handlerClick}>{this.state.number}</span>); }, handlerClick: function() { var newNum = this.state.number; newNum++; this.setState({number: newNum}); } });
上例中,這個組件創建了一個span元素,值為0。當我們每次點擊該元素時數字便會加一。
解讀分析下代碼,首先我們通過getInitialState申明了該組件的state,包含一個number屬性,初值為0。
在handlerClick的事件里我有一個"多余"的步驟,明明可以簡化寫成:
this.setState({number: ++this.state.number});
而我先用一個中間變量newNum保存了state里的number屬性值,在newNum的基礎上更改。
這里就是有關state很重要的一點:絕對不要直接更改state的值,只通過setState來改變。否則會因為多個地方多次對state更改,導致不統一。從而引發一些不必要的問題。
當state里有多個屬性,如果需要更新某一個組件不用更新state里所有的屬性,只更新需要的就好:
{name: 'lilei', age: 25, sex: '男'} //state this.setState({name: 'hanmeimei', sex: '女'}); //state:{name: 'hanmeimei', age: 25, sex: '女'}
更新時機
既然setState是React的扳機,那它就不能隨便在哪里都開槍。可能這部分東西需要對React的生命周期有一定掌握,許多文檔和博客里都寫得很詳細。我這里就不再抄書了。
通常調用setState都是在人工觸發的事件里,比如上例中的handlerClick。但總有需要自動觸發的情形。生命周期主要分為創建、更新和銷毀三個階段。
- 首先,在任何階段的render函數里都是不可以調用setState來觸發更新的。
- 創建階段,一般是在componentWillMount以及componentDidMount這兩個生命周期函數中調用,前者表示React即將渲染真實DOM前的一個階段,也是最后的修改state的機會。后者表示真實DOM已經渲染完成,在頁面中能看到我們的組件了,這里再調用setState就會觸發組件的一次更新。在實際開發中通常用在下面這種情況:
var Foo = React.createClass({ getInitialState:function() { return {....}; }, render: function() { return (<..../>); }, componentDIdMount: function(){ AJAX { this.setState({....});
} } });
大意就是首先創建出頁面元素,在componentDidMount函數中發起ajax之類的請求,獲取數據后通過setState更新頁面將數據更新到頁面中。
這樣做的好處就是在請求較慢或者請求失敗的情況下,頁面不至於留白,影響用戶體驗。
- 更新階段,絕...對...不...要...在...這...個...階...段...調...用。因為如果在該階段任意一個生命周期函數中使用setState觸發頁面更新時,組件又會再次進入生命周期的更新階段,這里會再次調用setState方法,然后進入死循環。
- 銷毀階段,就更不用說了,組件都沒得了,還更新個毛啊。
了解完了state,繼續看input的無約束組件。
<input defaultValue="123" />
如果設置了defaultValue屬性,該組件就是無約束組件。此時可以直接設置input的默認值,設置之后內容可以直接進行更改。缺點是這個屬性貌似只能設置一次,重復設置無效。
如果你不想為約束組件編寫如上那些繁瑣的過程,React提供了簡單的方法——mixin。
mixin
簡單來說mixin是用來抽象某一功能的工具,將邏輯抽象出來,使其可以在多個組件里復用。除了自定義以外,官方已經封裝了一系列的mixin組件,使用前需要引入react-with-addons文件。
var Form = React.createClass({ mixins: [React.addons.LinkedStateMixin], getInitialState: function() { return {userName: '', passWord: ''}; }, render: function() { return (<div> <form> <input type='text' valueLink={this.linkState('userName')} placeholder='用戶名' /> <input type='password' valueLink={this.linkState('passWord')} placeholder='密碼' /> </form> </div>); } });
如上引入mixin組件后,只需要在input的特殊屬性valueLink中調用this.linkState('屬性名'),之后每次對input內容的更改就會同步到組件state中同名的屬性中。
其實LinkedStateMixin內部的實現跟我們Test那個示例組件里是一樣的,看懂了那段代碼就能理解這個mixin插件的內部原理了。
*雖然使用mixin可以簡化書寫流程,但是使用這種方式往數據流中添加定制功能時,復雜度會增加,建議只在特定場景下使用。傳統的約束表單組件更加靈活。
下面介紹下其他表單組件的內容
Label
label元素是表單中很重要的一個部分,由於for在JavaScript中是一個保留字,所以在JSX中for屬性更改為htmlFor。
<label htmlFor='name'>姓名</label>
Textarea
與傳統的HTML相比,在React中,textarea被修改為更像input的形式。
<textarea value={this.state.value} />
textarea的約束組件的使用方法與input一致,同時也可以使用同一個mixin。
<textarea valueLink={this.linkState('value')} />
使用defaultValue屬性同樣可以將textarea變為無約束組件。
<textarea defaultValue='請輸入內容' />
Select
在React中select與textarea一樣,相比HTML也作了一些修改,使它們操作起來更簡便。
無約束組件:
<select defaultValue='B'> <option value='A'>AAA</option> <option value='B'>BBB</option> <option value='C'>CCC</option> </select>
約束組件:
var SelectComponent= React.createClass({ getInitialState: function() { return {option: 'A'}; }, render: function() { return (<select value={this.state.option} onChange={this.handlerChange}> <option value='A'>A</option> <option value='B'>B</option> <option value='C'>C</option> </select>); }, handlerChange: function(event) { this.setState({option: event.target.value}); }
單選
約束組件:
var Radio = React.createClass({ getInitialState: function() { return {gender: '男'}; }, render: function() { return (<div> <input type='radio' name='gender' value='男' checked={this.state.sex == '男'} onChange={this.handlerChange} />男 <input type='radio' name='gender' value='女' checked={this.state.sex == '女'} onChange={this.handlerChange} />女 </div>); }, handlerChange: function(event) {
this.setState({gender: event.target.value}); } });
設置單選的defaultChecked會使其變為無約束組件。
<input type='radio' defaultChecked='true' />
復選
約束組件:
var CheckBox = React.createClass({ getInitialState: function() { return {basketBall: false, swim: false, sing: false}; }, render: function() { return (<div>
<p>愛好:</p> <input type='checkbox' checked={this.state.basketBall} value='basketBall' onChange={this.handlerChange} />籃球 <input type='checkbox' checked={this.state.swim} value='swim' onChange={this.handlerChange} />游泳
<input type='checkbox' checked={this.state.sing} value='sing' onChange={this.handlerChange} />唱歌 </div>); }, handlerChange: function(event) {
var type = event.target.value,
checked = event.target.checked,
newState = {};
newState[type] = checked;
this.setState(newState); } });
在handlerCheck函數中有一點要注意,我創建了一個中間變量newState。
handlerCheck: function(event) {
var type = event.target.value,
checked = event.target.value;
this.setState(type: checked); //state: {basketBall: false, swim: false, sing: false, type: true} }
如果像上面的寫法,type並不會作為變量,而是作為字符串解析。每當你在setState時遇到困難時,嘗試中間變量,這方法百試不爽。
無約束組件:
<input type='checkbox' defaultChecked='true' />
多表單元素與change事件處理
在實際開發中通常有多個表單組件,為了使一個change處理器能處理所有的表單組件變化,可以使用bind方法來綁定類型。
var FormComponent = React.createClass({ getInitialState: function() { return {name: '', gender: '男'}; }, render: function() { return (<form> <input type='text' value={this.state.name} onChange={this.handlerChange.bind(this,'name')} /> <label htmlFor='male'>男</label> <input id='male' name='gender' type='radio' value='男' checked={this.state.gender == '男'} onChange={this.handlerChange.bind(this,'gender')} /> <label htmlFor='female'>女</label> <input id='female' name='gender' type='radio' value='女' checked={this.state.gender == '女'} onChange={this.handlerChange.bind(this,'gender')} /> </form>); }, handlerChange: function(type, event) { var newState = {}; newState[type] = event.target.value; this.setState(newState); } });
表單是React初學者很容易踩的大坑,但是對表單組件的學習可以很快的理解state屬性。
感謝您的瀏覽,希望有所幫助。