上一篇講了 React 兩種最常見的元件:受控元件和非受控元件。為了可用性,我們一般編寫出來的元件希望支援這兩種特性:可以通過元件自身的方法來改變元件的某(些)狀態,也可以通過 props 的值的變化來改變元件自身的同一個(些)狀態。
元件改變自己的狀態只能通過改變 state 完成,而把 props 的變化反映到 state 卻是可以通過生命週期函式來實現。首先還是拿上一篇中受控 alert 元件程式碼為例:
class Alert extends React.Component {
constructor( props ) {
super( props )
this.state = {
content: ``,
show: false
}
this.show = ( content )=>{
this.setState( {
content: content,
show: true
} )
}
this.hide = ()=>{
this.setState( {
show: false
} )
}
}
render() {
let style = {
display: this.state.show ? `fixed` : `none`
}
return (
<div class="my-alert" style={ style } >
<div class="my-alert-tit">Alert</div>
<div>{ this.state.content }</div>
<div class="my-alert-footer">
<button onClick={ this.hide }>確定</button>
</div>
</div>
);
}
}
元件初始化的時候建構函式會接受傳入的 props ,而當元件的容器改變傳入元件的 props 的值時會觸發元件的 componentWillReceiveProps 的方法,在這個方法中我們可以把變化後的 props(nextProps) 通過 setState 對映成 state 的變化。那麼我們需要做的就是給受控元件增加初始化 props 處理和在 componentWillReceiveProps 內 props 的處理。
class Alert extends React.Component {
constructor( props ) {
super( props )
this.state = {
content: this.props.content || ``,
show: this.props.show || false
}
this.show = ( content )=>{
this.setState( {
content: content,
show: true
} )
}
this.hide = ()=>{
this.setState( {
show: false
} )
}
}
componentWillReceiveProps( nextProps ) {
this.setState( nextProps );
}
render() {
let style = {
display: this.state.show ? `fixed` : `none`
}
return (
<div class="my-alert" style={ style } >
<div class="my-alert-tit">Alert</div>
<div>{ this.state.content }</div>
<div class="my-alert-footer">
<button onClick={ this.hide }>確定</button>
</div>
</div>
);
}
}
那麼針對同一個 alert 元件的使用就變得多樣化,可以根據自己專案的需求來變化。譬如:
import { Alert } from `Alert`;
class App extends React.Component {
constructor() {
super();
this.state = {
alertMsg: ``,
showAlert: false
}
this.saveHandler = ()=>{
// save ajax success
this.refs.myAlert.show( `Save successfully` );
}
this.removeHandler = ()=>{
// remove ajax success
this.setState( {
alertMsg: `Remove successfully`,
showAlert: true
} )
}
}
render() {
<div>
<button onClick={ this.saveHandler }>Save</button>
<button onClick={ this.removeHandler }>Remove</button>
<Alert ref="myAlert" content={ this.state.alertMsg } show={ this.state.showAlert }/>
</div>
}
}
為了讓元件更健壯,我們對 state 和 props 的一些必須的初始化值(預設值)需要明確指定
class Alert extends React.Component {
constructor( props ) {
super( props )
let content = this.props.content;
let show = this.props.show;
/*
props.xxx 的優先順序比 props.defautXxx 高,
如果設定了props.xxx 則 props.defaultXxx 就不起作用
*/
this.state = {
content: content === undefined ? this.props.defaultContent : content
show: show === undefined ? this.props.defaultShow : show
}
}
}
Alert.propTypes = {
defaultShow: React.PropTypes.bool,
defaultContent: React.PropTypes.string,
show: React.PropTypes.bool,
content: React.PropTypes.string
}
Alert.defaultProps = {
defaultShow: false,
defaultContent: ``
}
如上程式碼如果對 props.xxx 和 props.defaultXxx 有迷惑的童鞋,其實有了 xxx 完全沒有必要再有 defaultXxx,但是參考一些元件庫的 api 設計,我理解為是為了保持受控元件 api 的統一性,如果把 alert 元件當成受控元件則初始化使用 defaultXxx,如果當成非受控元件就直接使用 xxx。
那什麼時候使用受控元件,什麼時候使用非受控元件呢?我們知道受控元件是比較符合我們傳統 UI 元件開發的思路的。但是 React 在跨元件通訊方面很弱,如果不借助第三方庫進行通訊,對於兩個毫無關係的元件相互呼叫就需要傳遞層層的回撥函式。我想沒有人喜歡這種程式設計風格,所以把所有元件的狀態抽象到一個地方進行集中管理變化,典型的資料流用 redux 就傾向於使用非受控元件了(這裡不討論flux思想的由來,不討論redux好壞)。
故最基本的 React 元件編寫套路就這些。但是這些還只是 api 應用層面的東西,比較難的是在編寫元件時候對狀態的抽象,使使用者使用的舒服自然。