React元件編寫思路(二)

sixwinds發表於2017-05-12

上一篇講了 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 應用層面的東西,比較難的是在編寫元件時候對狀態的抽象,使使用者使用的舒服自然。

相關文章