React躬行記(3)——組件
組件(Component)由若干個React元素組成,包含屬性、狀態和生命周期等部分,滿足獨立、可復用、高內聚和低耦合等設計原則,每個React應用程序都是由一個個的組件搭建而成,即組成React應用程序的最小單元正是組件。
一、構建
目前推崇的構建組件的方式總共有兩種:類和函數,而用React.createClass()構建組件的方式已經過時,本節也不會對其做講解。
1)類組件
通過ES6新增的類構建而成的組件必須繼承自React.Component,並且需要定義render()方法。此方法用於組件的輸出,即組件的渲染內容,如下代碼所示。注意,render()是一個純函數,不會改變組件的狀態,並且其返回值有多種,包括React元素、布爾值、數組等。
class Btn extends React.Component {
render() {
return <button>提交</button>;
}
}
2)函數組件
使用函數構建的組件只關注用戶界面的展示,既無狀態,也無生命周期。其功能相當於類組件的render()方法,但能接收一個屬性對象(props),下面是一個簡單的函數組件。
function Btn(props) {
return <button>{props.text}</button>;
}
與類組件不同,函數組件在調用時不會創建新實例。
二、state(組件狀態)
組件中的state用於記錄其內部狀態,這類有狀態組件會隨著state的變化修改其最終的呈現。
1)初始化
在組件的構造函數constructor()中可以像下面這樣,通過this.state初始化組件的內部狀態,其中this.state必須是一個對象。
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(組件屬性)
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);
});
當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。