組件(Component)由若干個React元素組成,包含屬性、狀態和生命週期等部分,知足獨立、可複用、高內聚和低耦合等設計原則,每一個React應用程序都是由一個個的組件搭建而成,即組成React應用程序的最小單元正是組件。node
目前推崇的構建組件的方式總共有兩種:類和函數,而用React.createClass()構建組件的方式已通過時,本節也不會對其作講解。git
1)類組件數組
經過ES6新增的類構建而成的組件必須繼承自React.Component,而且須要定義render()方法。此方法用於組件的輸出,即組件的渲染內容,以下代碼所示。注意,render()是一個純函數,不會改變組件的狀態,而且其返回值有多種,包括React元素、布爾值、數組等。dom
class Btn extends React.Component { render() { return <button>提交</button>; } }
2)函數組件異步
使用函數構建的組件只關注用戶界面的展現,既無狀態,也無生命週期。其功能至關於類組件的render()方法,但能接收一個屬性對象(props),下面是一個簡單的函數組件。函數
function Btn(props) { return <button>{props.text}</button>; }
與類組件不一樣,函數組件在調用時不會建立新實例。優化
組件中的state用於記錄其內部狀態,這類有狀態組件會隨着state的變化修改其最終的呈現。ui
1)初始化this
在組件的構造函數constructor()中能夠像下面這樣,經過this.state初始化組件的內部狀態,其中this.state必須是一個對象。spa
class Btn extends React.Component { constructor() { super(); this.state = { text: "提交" }; } render() { return <button>{this.state.text}</button>; } }
注意,在初始化以前要先調用super(),由於ES6對兩個類的this的初始化順序作了規定,先父類,再子類,因此super()方法要在使用this以前調用。
若是要讀取this.state中的數據,那麼能夠像上面的代碼那樣經過成員訪問運算符獲得。但若是要更新this.state中的數據,那麼就得用setState()方法,而不是用運算符。
2)setState()
此方法能接收2個參數,第一個是函數或對象,第二個是可選的回調函數,會在更新以後觸發。下面用示例講解第一個參數的兩種狀況(省略了構造函數以及初始化的代碼),當它是函數時,能接收2個參數,第一個是當前的state,第二個是組件的props(將在下一節講解),此處函數的功能是交替變換按鈕的文本。
class Btn extends React.Component { change() { this.setState((state, props) => { return { text: state.text == "點擊" ? "提交" : "點擊" }; }); } render() { return <button onClick={this.change.bind(this)}>{this.state.text}</button>; } }
當setState()方法的第一個參數是對象時,能夠將要更新的數據傳遞進來,就像下面這樣(省略了render()方法)。
class Btn extends React.Component { change() { this.setState({ text: "點擊" }); } }
setState()是一個異步方法,React會將多個setState()方法合併成一個調用,也就是說,在調用setState()後,不能立刻反映出狀態的變化。例如this.state.text的值原先是「提交」,在像下面這樣更新狀態後,打印出的值仍然是「提交」。
this.setState({text: "點擊"}); console.log(this.state.text); //"提交"
setState()方法在將新數據合併到當前狀態以後,就會自動調用render()方法,驅動組件從新渲染。由此可知,在render()方法中不容許調用setState()方法,以避免形成死循環。
在後面的生命週期一節中會講解組件在各個階段可用的回調函數。其中有些回調函數得在render()方法被執行後再被調用,所以在它們內部經過this.state獲得的將是更新後的內部狀態。
props(properties的縮寫)能接收外部傳遞給組件的數據,當組件做爲React元素使用時,props就是一個由元素屬性所組成的對象。以Btn組件爲例,它的props的結構以下所示,其中children是一個特殊屬性,後續將會單獨講解。
<Btn name="strick" digit={0}>提交</Btn> props = { name: "strick", digit: 0, children: "提交" }
1)讀取
每一個組件都會有一個構造函數,而它的參數正是props。因爲React組件至關於一個純函數,所以props不能被修改,它的屬性都是隻讀的,像下面這樣賦值勢必會引發組件的反作用,於是React會立刻終止程序,直接拋出錯誤。
class Btn extends React.Component { constructor(props) { super(props); props.name = "freedom"; //錯誤 } }
由於有此限制,因此若要修改props中的某個屬性,一般會先將它賦給state,再經過state更新數據,以下所示。
class Btn extends React.Component { constructor(props) { super(props); this.state = { name: props.name }; } }
在構造函數以外,可經過this.props訪問到傳遞進來的數據。
2)defaultProps
組件的靜態屬性defaultProps可爲props指定默認值,例如爲組件設置默認的name屬性,當props.name缺省時,就能用該值,以下所示。
class Btn extends React.Component { constructor(props) { super(props); } render() { return <button>{this.props.name}</button>; } } Btn.defaultProps = { name: "freedom" };
3)children
每一個props都會包含一個特殊的屬性:children,表示組件的內容,即所包裹的子組件。例以下面這個Btn組件,其props.children的值爲「搜索」。
<Btn>搜索</Btn>
children能夠是null、字符串或對象等數據類型,而且當組件的內容是多個子組件時,children還能自動變成一個數組。
官方經過React.Children給出了專門處理children的輔助方法,例如用於遍歷的forEach(),以下代碼所示,其他方法可參考表1。
React.Children.forEach(props.children, child => {
console.log(child);
});
表1 輔助方法
方法 | 描述 |
map() | 當children不是null或undefined時,遍歷children並返回一個數組,不然返回null或undefined |
forEach() | 功能與map()相似,但不返回數組 |
count() | 計算children的數量 |
only() | 當children是一個React元素時,返回該元素,不然拋出錯誤 |
toArray() | 將children轉換成數組 |
當children是數組時,React.Children中的map()和forEach()兩個方法與數組中的功能相似,而且count()方法的返回值與數組的length屬性相同。但當children是其它類型時,用React.Children中的輔助方法會比較保險,例如children爲一個子組件「strick」,調用count()方法獲得的值爲1,而調用length屬性獲得的值爲6,這與當前子組件的數量不符。
4)校驗屬性
自React v15.5起,官方棄用了React.PropTypes,改用prop-types庫。此庫能校驗props中屬性的類型,例如將Btn組件的age屬性限制爲數字,能夠像下面這樣設置。
Btn.propTypes = {
age: PropTypes.number
}
在引入該庫後,就會有一個全局對象PropTypes。除了數字類型以外,PropTypes還提供了其它類型的校驗,具體對應關係可參考表2。
表2 對應關係
PropTypes中的屬性 | 對應類型 |
PropTypes.array | 數組 |
PropTypes.bool | 布爾值 |
PropTypes.func | 函數 |
PropTypes.number | 數字 |
PropTypes.object | 對象 |
PropTypes.string | 字符串 |
PropTypes.symbol | 符號 |
PropTypes.node | 可被渲染的元素,例如數字、React元素等 |
PropTypes.element | React元素 |
PropTypes.elementType | React元素類型 |
當組件的屬性是對象或數組時,PropTypes能校驗其成員的類型,例如要求數組的成員都得是字符串、對象的某個屬性必須是布爾值,能夠像下面這樣操做。
Btn.propTypes = {
names: PropTypes.arrayOf(PropTypes.string),
person: PropTypes.shape({ isMan: PropTypes.bool })
}
PropTypes還能在其任意屬性後加isRequired標記,例如PropTypes.number.isRequired表示必須傳數字類型的屬性,而且不能缺省。下面示例中的school屬性,不限制類型,只要有值就行。
Btn.propTypes = {
school: PropTypes.any.isRequired
}
此節只列出了prop-types庫中的部分功能,其他功能可參考官方文檔。
5)數據流
在React中,組件之間的數據是自頂向下單向流動,即父組件經過props將數據傳遞給子組件(如圖3所示),以此實現它們之間的對話和聯繫。
圖3 單向數據流
舉個簡單的例子,有兩個組件Container和Btn,其中Container是父組件,Btn是子組件,Container組件會將它的text屬性傳遞給Btn組件,以此完成數據的流動。爲了便於觀察,省略了兩個組件的構造函數,具體以下所示。
class Btn extends React.Component { render() { return <button>{this.props.text}</button>; } } class Container extends React.Component { render() { return <Btn text="提交" />; } }
在組件中渲染列表數據是很是常見的須要,例如輸出多個按鈕,以下代碼所示。
class Btns extends React.Component { constructor(props) { super(props); } render() { const list = this.props.names.map(value => <button>{value}</button>); return <div>{list}</div>; } } ReactDOM.render( <Btns names={[1,2,3]}>按鈕列表</Btns>, document.getElementById("container") );
在上面的render()方法中,先經過map()方法遍歷傳遞進來的names屬性,再爲這個數組的每一個元素加上<button>標籤,最後獲得元素列表list後,將其做爲返回值輸出。不過,此時會收到一個要求爲列表中的子元素添加key屬性的警告,如圖4所示。
圖4 key屬性的警告
在React中,Keys會做爲元素的身份標識,可以幫助React識別出發生變化的元素,從而只渲染這些元素。每一個元素的key屬性在當前列表中要保持惟一性,即在其兄弟元素之間要獨一無二,以下代碼所示(省略了組件的構造函數)。
class Btns extends React.Component { render() { const list1 = this.props.names.map(value => <button key={value}>{value}</button>); const list2 = this.props.names.map(value => <button key={value}>{value}</button>); return ( <div> <section>{list1}</section> <section>{list2}</section> </div> ); } }
在上面的render()方法中,有兩個元素列表:list1和list2,雖然它們包含相同key屬性的<button>元素,但分別被嵌到了兩個<section>元素中,從而將二者隔離,達成了key屬性惟一的目標。由此可知,key屬性不是全局惟一的。
注意,通常不建議用數組的索引做爲key屬性的值,由於一旦數組中元素的位置發生變化,其索引也會跟着改變,不利於渲染優化。
關於在什麼時候設置key屬性,有個簡單的規則能夠參考,那就是當元素位於map()方法內時,須要爲該元素添加key屬性。