#ReactJS學習筆記——組件複合及表單的處理html
React是一個JavaScript庫文件,使用它的目的在於可以解決構建大的應用和數據的實時變動。該設計使用JSX容許你在構建標籤結構時充分利用JavaScript的強大能力,而沒必要在笨拙的模板語言上浪費時間。node
系統環境:window x86_64react
命令行工具:git-bashgit
React版本:React v0.14.7github
##1 組件的複合 在傳統的HTML中,元素是構成頁面的基礎單元。可是在React中,構建頁面的基礎單元是React組件。你能夠把React組件理解成爲混入了JavaScript表達能力的HTML元素。實際上寫React代碼就是構建組件,就像編寫HTML文檔時使用元素同樣。 本質上,一個組件就是一個JavaScript函數,它接受屬性(props)和(state)做爲參數,並輸出渲染好的HTML。組件通常被用來呈現和表達應用的某部分數據,所以你能夠把React組件理解爲HTML元素的拓展。 React+JSX是強大而富有表現力的工具,容許咱們使用相似HTML的語法建立自定義元素,比起單純的HTML,他們還可以控制僧命週期中的行爲。這些都在從React.createClass
方法開始的。相較於繼承ES6已經開始支持,實現多個小巧、簡單的組件和數據對象,構形成大而複雜的組件。數組
###1.1 組件複合的例子瀏覽器
HTML提供了一些基本的元素——單選類型是輸入框和表單組(input group),能夠在這裏使用。組件的層級從上往下看是這樣的: MultipleChoice - RadioInput - Input (type="radio")
從先日後的順序。選擇題組件MultipleChoice"有一個"單選框RadioInput,單選框RadioInput」有一個「輸入框元素Input。這裏組合模式(composition pattern)的特性。bash
###1.2 組裝HTML單選框RadioInput 依照從下往上的設計規則,咱們首先須要組裝一個RadioInput組件,這個組件使用了通用的input,,將其精縮成與單選按鈕行爲一致的組件。 ####1.2.1 添加動態屬性 咱們知道input尚未內容是動態的,咱們須要定義一些可以有父元素傳遞給單選框的一些屬性。框架
####1.2.2 代碼分析 RadioInput.js
dom
var React = require('react'); var uniqueId = require('lodash-node/modern/utility/uniqueId'); var RadioInput = React.createClass({ // 1.添加動態屬性 propTypes: { name: React.PropTypes.string.isRequired, value:React.PropTypes.string.isRequired, checked:React.PropTypes.bool, onChanged:React.PropTypes.func.isRequired }, // 2.爲非必要屬性定義其默認值 getDefaultProps: function() { return { checked: false } }, // 3.追蹤狀態,組件須要記錄隨時間而變化的數據 getInitialState: function() { var name = this.props.name ? this.props.name:uniqueId('radio-'); return { checked: !!this.props.checked, name: name } }, // 4.追蹤當前組件的狀態變動,並經過this.props.onChanged通知給父組件 handleChanged:function(e) { var checked = e.target.checked; this.setState({checked:checked}); if(checked) { this.props.onChanged(this.props.value); } }, render:function() { return( <div className="radio"> <label htmlFor={this.props.id}> <input type="radio" name={this.props.name} value={this.props.value} checked={this.props.checked} onChange={this.handleChanged} /> {this.props.value} </label> </div> ); } }); module.exports = RadioInput;
代碼總共分爲5個步驟,這是繪製一個組件的基本流程:
isReauired
,若是父元素沒有對isReauired
的屬性進行聲明,運行時會產生警告。RadioInput中必需的屬性包含:name、value、onChanged(函數),具體類型聲明參考:https://facebook.github.io/react/docs/reusable-components.htmlgetDefaultProps
進行初始化。getInitialState
中聲明組件內的變量,記錄者組件的數據變動,能夠經過setState方法修改其內容。handleChanged:function(e)
(函數名能夠自定義),在handleChanged:function(e)
能夠看到對屬性函數onChanged方法this.props.onChanged(this.props.value);
的調用,這裏能夠將組件的事件傳遞至父組件,由父組件相應當前子組件的變化。橋接了子組件和父組件之間的關係。###1.3 父組件對子組件的整合
####1.3.1 設計思考 父組件指望組合一個單選組合,這一層的主要做用是渲染一列選項讓用戶從中選擇。這裏仍是按照以前設計RadioInput的設計邏輯:
getDefaultProps
進行初始化,這裏不須要。getInitialState
中聲明組件內的變量,這裏包含一個id和一個value。具體使用參考代碼。setState
並把事件對外傳遞經過this.props.onComplete
####1.3.2 代碼分析 MutileChoice.js
var React = require('react'); var RadioInput = require('./RadioInput'); var uniqueId = require('lodash-node/modern/utility/uniqueId'); // 父組件 var MutileChoice = React.createClass({ // 1.添加動態屬性 propTypes: { value: React.PropTypes.string, choices: React.PropTypes.array.isRequired, onComplete: React.PropTypes.func.isRequired }, // 3.追蹤狀態,組件須要記錄隨時間而變化的數據 getInitialState: function() { return { id: uniqueId('multiple-choice-'), value: this.props.value } }, // 4.響應事件 handleChanged: function(value) { this.setState({value:value}); this.props.onComplete(value); }, renderChoices: function() { var SquareItemFactory = React.createFactory(RadioInput); return this.props.choices.map(function(choice, i) { // return AnswerRadioInput({ // id:"choice-"+i, // name:this.state.id, // label:choice, // value:choice, // checked:this.state.value === choice, // onChanged: this.handleChanged // }); return SquareItemFactory({ key:"choice-"+i, name:this.state.id, value:choice, checked:this.state.value === choice, onChanged: this.handleChanged }); }.bind(this)); }, render: function() { return( <div className="form-group-choice"> {this.renderChoices()} </div> ); } }); module.exports = MutileChoice;
代碼中須要留意兩個地方:(1)map使用;(2)註釋代碼中存在的問題。
Array.prototype.map()
,map是對array的每個元素進行遍歷,arr.map(callback[, thisArg])
其中callback參數有三個(可選):currentValue:當前值,index當前元素索引,array當前數組;thisArg參數定義爲:填入值爲this,默認爲window對象。map參考 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
renderChoices: function() { var SquareItemFactory = React.createFactory(RadioInput); return this.props.choices.map(function(choice, i) { // return RadioInput({ // id:"choice-"+i, // name:this.state.id, // label:choice, // value:choice, // checked:this.state.value === choice, // onChanged: this.handleChanged // }); return SquareItemFactory({ key:"choice-"+i, name:this.state.id, value:choice, checked:this.state.value === choice, onChanged: this.handleChanged }); }.bind(this)); },
若是代碼中,直接構建實例,在React v0.14.7環境下,會報出以下: Uncaught TypeError: Cannot read property '__reactAutoBindMap' of undefined
###1.4 使用已經封裝好的組件 子組件和父組件都已經封裝完畢,如何使用父組件來實現單選功能?這裏只須要在實現代碼中使用MutileChoice標籤,同時定義相應的標籤屬性,代碼以下所示:
var React = require('react'); var ReactDOM = require("react-dom"); var AnswerMutileChoice = require('./MutileChoice'); var choices = [ "遼寧民間藝術團隊", "開心麻花","賈玲團隊","曹雲金團隊" ]; function handleComplete(value) { console.log("handleComplete " + value); } ReactDOM.render( <div className="root"> <label htmlFor="firstQuestion">你最喜歡的歡樂喜劇人?</label> <AnswerMutileChoice choices={choices} onComplete={handleComplete}/> <button>提交</button> </div> , document.body);
效果參考下圖所示:
##2 表單使用 表單是應用必不可少的一部分,只要須要用戶輸入,哪怕是最簡單的輸入,都離不開表單。一直以來,單頁應用的表單都很難處理好,由於表單充斥着用戶變化莫測的狀態,要管理好這些狀態是很費神的,也很容易出現bug。React能夠幫助你管理應用中的狀態,天然也包括表單在內。 如今,你應該知道React組件的核心離你那就是可預知性和可測試性。給定一樣的props和state,任何React組件都會渲染出同樣的結果。表單也不例外。 在React中,表單有兩種類型:約束組件和無約束組件。
###2.1 無約束組件 無約束表單的構造與React中大多數組件相比是反模式。在HTML中,表單組件與React組件行爲並不一致。給定HTML的<input/>
一個值,這個<input/>
值還是能夠改變的。這正是無約束組件名稱的由來,由於表單組件的值是不受React組件控制的。若是想訪問它的值,須要給<input/>
添加一個ref屬性,以訪問DOM節點的值。 ref是一個不屬於DOM屬性的特殊屬性,用來標記DOM節點,能夠經過this上下文訪問這個節點。爲了便於訪問,組件的全部的ref都添加到了this.refs上。 下面咱們在表單中添加一個<input/>
,並在表單提交時訪問它的值。
var React = require('react'); var ReactDOM = require("react-dom"); var MyForm = React.createClass({ submitHandler:function(event) { event.preventDefault(); // 經過ref訪問輸入框 var helloTo = this.refs.helloTo.getDOMNode().value; alert(helloTo); }, render:function() { return ( <form onSubmit={this.submitHandler}> <input ref="helloTo" type="text" defaultValue="hello world!" /> <br /> <button type="submit">Speak</button> </form> ); } }); ReactDOM.render(<div className="root"><MyForm /></div>, document.body);
無約束組件能夠用在基本的無需任何驗證或者輸入控制的表單中,當指望用戶在輸入的時候檢測輸入的變化的須要使用約束組件。
###2.2 約束組件 約束組件的模式與React其餘類型的組件的模式一致。表單組件的狀態交由React組件的控制,狀態值被存儲在React組件的state中。在約束組件中,輸入框的值是由父組件設置的。咱們對2.1中的代碼進行改造,改爲約束組件:
var MyForm = React.createClass({ // 1.定義默認值 getInitialState:function() { return { helloTo:"hello world!!!" }; }, // 2.處理輸入變化 handleChange:function(event) { this.setState({ helloTo:event.target.value }); }, submitHandler:function(event) { event.preventDefault(); alert(this.state.helloTo); }, // 3.渲染時value值使用state保存 render:function() { return ( <form onSubmit={this.submitHandler}> <input type="text" value={this.state.helloTo} onChange={this.handleChange}/> <br /> <button type="submit">Speak</button> </form> ); } }); ReactDOM.render(<div className="root"><MyForm /></div>, document.body);
顯著的變化就是</input>
的值存儲在父組件的state中。由於數據流有了清晰的定義。
</input>
,其值onChange時,change處理器被調用。</input>
的值。相比於無約束組件相比,代碼量增長了很多,可是如今能夠控制數據流,在用戶輸入數據的時候更新state。譬如想在用戶輸入的時候將字符都轉成大寫。
handleChange:function(event) { this.setState({ helloTo:event.target.value.toUpperCase() }); },
這樣咱們能夠限制可輸入的字符集,或者限制用戶想郵件地址輸入框輸入不合法的字符。 你還能夠在用戶輸入數據時,把他們用在其餘的組件上。例如:
###2.3 表單元素的name屬性 在React中,name屬性對於表單元素來講並無那麼重要,由於約束表單組件已經把值存儲到了state中,而且表單提交事件也會被攔截。在獲取表單值的時候,name屬性並非必需的。對於非約束組件的表單來講,也可使用refs來直接訪問表單元素。 即使如此,name仍然是表單組件中很是重要的一部分。
###2.4 多個表單元素與change處理器 在使用約束的表單組件時,沒有人願意重複地爲每個組件編寫change處理器,還好有幾種方式能夠在React中重用一個事件處理器。 示例一:經過.bind
傳遞其餘參數。
onChange={this.handleChange.bind(this, 'given_name')}
示例二:使用DOMNode的name屬性來判斷須要更新哪一個組件的狀態 組件name="given_name" 提供state的given_name,而後經過以下代碼匹配:
handleChange: function(event) { var newState = {}; newState[event.target.name] = event.target.value; this.setState(newState); },
示例三:React還在addon中提供了一個mixin,React.addons.LinkedStateMixin經過另外一種方式解決一樣的問題。
###2.5 自定義表單組件 自定義組件是一種極好方式,能夠在項目中複用共有的功能。同時,也不失爲一種將交互界面提高爲更加複雜的表單組件(好比複選框組件或單選框組件)的好方法。 當編寫自定義組件時,接口應當與其餘表單組件保持一致。這能夠幫助用戶理解代碼,明白如何使用自定義組件,且無須深刻到組件的實現細節裏。 咱們來建立一個自定義的單選框組件,其接口與React的select組件保持一致。咱們不打算實現多選功能,由於單選框組件原本就不支持多選。
var Radio = React.createClass({ // 初始化屬性 propTypes:{ onChange: React.PropTypes.func }, // 初始化state getInitialState:function() { return { value:this.props.defaultValue }; }, // 事件處理 handleChange:function(event) { if(this.props.onChange) { this.props.onChange(event); } this.setState({ value:event.target.value }); }, render:function() { var children = []; var value = this.props.value || this.state.value; React.Children.forEach(this.props.children, function(child, i) { console.log("children " + child.props.value +" " +child.props.children); var label = ( <label> <input type = "radio" name={this.props.name} value={child.props.value} checked={child.props.value == value} onChange={this.handleChange} /> {child.props.children} <br/> </label> ); children[i] = label; }.bind(this)); return( <div> { children.map(function (name) { return <div>{name}</div> }) } </div> ); } });
經過上面的模塊,就能夠實現任意幾個類型爲radio的input組件自定義,在父組件中調用代碼爲:
render:function() { return ( <form onSubmit={this.submitHandler}> <Radio name="my_radio" value={this.state.my_radio} onChange={this.handleChange} > <option value="A">First option</option> <option value="B">Second option</option> <option value="C">Thrid option</option> </Radio> <button type="submit">Speak</button> </form> ); }
在自定義模塊render方法的return中,這裏處理的不是很好,增長了兩個div標籤,暫時沒想到好的辦法,若您有好的辦法,能夠給我留言。
###2.6 Focus 控制表單組件的focus能夠很好地引導用戶按照表單邏輯逐步填寫,並且還能夠減小用戶的操做,加強可用性,加強可用性, 由於React的表單並不老是在瀏覽器加載時被渲染,因此表單的輸入域的自動聚焦操做起來有點不同。React實現了autoFocus屬性,所以在組建第一次掛載時,若是沒有其餘的表單域聚焦時,React就會把焦點放在這個組件對應的表單域中。以下代碼:
<input type="text" name="given_name" autoFocus="true"/>
還有一種方法就是調用DOMNode的focus()方法,手動設置表單域聚焦。
##3 可用性 React雖然能夠提供開發者的生產力,可是也有不盡如人意的地方。主要注意如下幾點:
##4 參考
《React 引領將來的用戶界面開發框架》
https://facebook.github.io/react/docs/transferring-props.html
https://facebook.github.io/react/docs/reusable-components.html