React.js 小書 Lesson11 - 配置元件的 props

weixin_34391445發表於2017-11-09

React.js 小書 Lesson11 - 配置元件的 props

本文作者:鬍子大哈
本文原文:http://huziketang.com/books/react/lesson11

轉載請註明出處,保留原文連結以及作者資訊

線上閱讀:http://huziketang.com/books/react


元件是相互獨立、可複用的單元,一個元件可能在不同地方被用到。但是在不同的場景下對這個元件的需求可能會根據情況有所不同,例如一個點贊按鈕元件,在我這裡需要它顯示的文字是“點贊”和“取消”,當別的同事拿過去用的時候,卻需要它顯示“贊”和“已贊”。如何讓元件能適應不同場景下的需求,我們就要讓元件具有一定的“可配置”性。

React.js 的 props 就可以幫助我們達到這個效果。每個元件都可以接受一個 props 引數,它是一個物件,包含了所有你對這個元件的配置。就拿我們點贊按鈕做例子:

[圖片上傳失敗...(image-a90954-1510226306876)]

下面的程式碼可以讓它達到上述的可配置性:

class LikeButton extends Component {
  constructor () {
    super()
    this.state = { isLiked: false }
  }

  handleClickOnLikeButton () {
    this.setState({
      isLiked: !this.state.isLiked
    })
  }

  render () {
    const likedText = this.props.likedText || '取消'
    const unlikedText = this.props.unlikedText || '點贊'
    return (
      <button onClick={this.handleClickOnLikeButton.bind(this)}>
        {this.state.isLiked ? likedText : unlikedText} ?
      </button>
    )
  }
}

render 函式可以看出來,元件內部是通過 this.props 的方式獲取到元件的引數的,如果 this.props 裡面有需要的屬性我們就採用相應的屬性,沒有的話就用預設的屬性。

那麼怎麼把 props 傳進去呢?在使用一個元件的時候,可以把引數放在標籤的屬性當中,所有的屬性都會作為 props 物件的鍵值

class Index extends Component {
  render () {
    return (
      <div>
        <LikeButton likedText='已贊' unlikedText='贊' />
      </div>
    )
  }
}

就像你在用普通的 HTML 標籤的屬性一樣,可以把引數放在表示元件的標籤上,元件內部就可以通過 this.props 來訪問到這些配置引數了。

[圖片上傳失敗...(image-401123-1510226306876)]

前面的章節我們說過,JSX 的表示式插入可以在標籤屬性上使用。所以其實可以把任何型別的資料作為元件的引數,包括字串、數字、物件、陣列、甚至是函式等等。例如現在我們把一個物件傳給點贊元件作為引數:

class Index extends Component {
  render () {
    return (
      <div>
        <LikeButton wordings={{likedText: '已贊', unlikedText: '贊'}} />
      </div>
    )
  }
}

現在我們把 likedTextunlikedText 這兩個引數封裝到一個叫 wordings 的物件引數內,然後傳入點贊元件中。大家看到 {{likedText: '已贊', unlikedText: '贊'}} 這樣的程式碼的時候,不要以為是什麼新語法。之前討論過,JSX 的 {} 內可以嵌入任何表示式,{{}} 就是在 {} 內部用物件字面量返回一個物件而已。

這時候,點贊按鈕的內部就要用 this.props.wordings 來獲取到到引數了:

class LikeButton extends Component {
  constructor () {
    super()
    this.state = { isLiked: false }
  }

  handleClickOnLikeButton () {
    this.setState({
      isLiked: !this.state.isLiked
    })
  }

  render () {
    const wordings = this.props.wordings || {
      likedText: '取消',
      unlikedText: '點贊'
    }
    return (
      <button onClick={this.handleClickOnLikeButton.bind(this)}>
        {this.state.isLiked ? wordings.likedText : wordings.unlikedText} ?
      </button>
    )
  }
}

甚至可以往元件內部傳入函式作為引數:

class Index extends Component {
  render () {
    return (
      <div>
        <LikeButton
          wordings={{likedText: '已贊', unlikedText: '贊'}}
          onClick={() => console.log('Click on like button!')}/>
      </div>
    )
  }
}

這樣可以通過 this.props.onClick 獲取到這個傳進去的函式,修改 LikeButtonhandleClickOnLikeButton 方法:

...
  handleClickOnLikeButton () {
    this.setState({
      isLiked: !this.state.isLiked
    })
    if (this.props.onClick) {
      this.props.onClick()
    }
  }
...

當每次點選按鈕的時候,控制檯會顯示 Click on like button! 。但這個行為不是點贊元件自己實現的,而是我們傳進去的。所以,一個元件的行為、顯示形態都可以用 props 來控制,就可以達到很好的可配置性。

預設配置 defaultProps

上面的元件預設配置我們是通過 || 操作符來實現。這種需要預設配置的情況在 React.js 中非常常見,所以 React.js 也提供了一種方式 defaultProps,可以方便的做到預設配置。

class LikeButton extends Component {
  static defaultProps = {
    likedText: '取消',
    unlikedText: '點贊'
  }

  constructor () {
    super()
    this.state = { isLiked: false }
  }

  handleClickOnLikeButton () {
    this.setState({
      isLiked: !this.state.isLiked
    })
  }

  render () {
    return (
      <button onClick={this.handleClickOnLikeButton.bind(this)}>
        {this.state.isLiked
          ? this.props.likedText
          : this.props.unlikedText} ?
      </button>
    )
  }
}

注意,我們給點贊元件加上了以下的程式碼:

  static defaultProps = {
    likedText: '取消',
    unlikedText: '點贊'
  }

defaultProps 作為點贊按鈕元件的類屬性,裡面是對 props 中各個屬性的預設配置。這樣我們就不需要判斷配置屬性是否傳進來了:如果沒有傳進來,會直接使用 defaultProps 中的預設屬性。 所以可以看到,在 render 函式中,我們會直接使用 this.props 而不需要再做判斷。

props 不可變

props 一旦傳入進來就不能改變。修改上面的例子中的 handleClickOnLikeButton

...
  handleClickOnLikeButton () {
    this.props.likedText = '取消'
    this.setState({
      isLiked: !this.state.isLiked
    })
  }
...

我們嘗試在使用者點選按鈕的時候改變 this.props.likedText ,然後你會看到控制檯報錯了:

[圖片上傳失敗...(image-30fcab-1510226306876)]

你不能改變一個元件被渲染的時候傳進來的 props。React.js 希望一個元件在輸入確定的 props 的時候,能夠輸出確定的 UI 顯示形態。如果 props 渲染過程中可以被修改,那麼就會導致這個元件顯示形態和行為變得不可預測,這樣會可能會給元件使用者帶來困惑。

但這並不意味著由 props 決定的顯示形態不能被修改。元件的使用者可以主動地通過重新渲染的方式把新的 props 傳入元件當中,這樣這個元件中由 props 決定的顯示形態也會得到相應的改變。

修改上面的例子的 Index 元件:

class Index extends Component {
  constructor () {
    super()
    this.state = {
      likedText: '已贊',
      unlikedText: '贊'
    }
  }

  handleClickOnChange () {
    this.setState({
      likedText: '取消',
      unlikedText: '點贊'
    })
  }

  render () {
    return (
      <div>
        <LikeButton
          likedText={this.state.likedText}
          unlikedText={this.state.unlikedText} />
        <div>
          <button onClick={this.handleClickOnChange.bind(this)}>
            修改 wordings
          </button>
        </div>
      </div>
    )
  }
}

在這裡,我們把 Indexstate 中的 likedTextunlikedText 傳給 LikeButtonIndex 還有另外一個按鈕,點選這個按鈕會通過 setState 修改 Indexstate 中的兩個屬性。

由於 setState 會導致 Index 重新渲染,所以 LikedButton 會接收到新的 props,並且重新渲染,於是它的顯示形態也會得到更新。這就是通過重新渲染的方式來傳入新的 props 從而達到修改 LikedButton 顯示形態的效果。

總結

  1. 為了使得元件的可定製性更強,在使用元件的時候,可以在標籤上加屬性來傳入配置引數。
  2. 元件可以在內部通過 this.props 獲取到配置引數,元件可以根據 props 的不同來確定自己的顯示形態,達到可配置的效果。
  3. 可以通過給元件新增類屬性 defaultProps 來配置預設引數。
  4. props 一旦傳入,你就不可以在元件內部對它進行修改。但是你可以通過父元件主動重新渲染的方式來傳入新的 props,從而達到更新的效果。

下一節中我們將介紹《React.js 小書 Lesson12 - state vs props》

相關文章