• 默认配置 defaultProps
  • props 不可变
  • 总结
  • 课后练习
    • 作者:胡子大哈
    • 原文链接: http://huziketang.com/books/react/lesson11
    • 转载请注明出处,保留原文链接和作者信息。

    组件是相互独立、可复用的单元,一个组件可能在不同地方被用到。但是在不同的场景下对这个组件的需求可能会根据情况有所不同,例如一个点赞按钮组件,在我这里需要它显示的文本是“点赞”和“取消”,当别的同事拿过去用的时候,却需要它显示“赞”和“已赞”。如何让组件能适应不同场景下的需求,我们就要让组件具有一定的“可配置”性。

    React.js 的 props 就可以帮助我们达到这个效果。每个组件都可以接受一个 props 参数,它是一个对象,包含了所有你对这个组件的配置。就拿我们点赞按钮做例子:

    React.js 小书教程图片

    下面的代码可以让它达到上述的可配置性:

    1. class LikeButton extends Component {
    2. constructor () {
    3. super()
    4. this.state = { isLiked: false }
    5. }
    6. handleClickOnLikeButton () {
    7. this.setState({
    8. isLiked: !this.state.isLiked
    9. })
    10. }
    11. render () {
    12. const likedText = this.props.likedText || '取消'
    13. const unlikedText = this.props.unlikedText || '点赞'
    14. return (
    15. <button onClick={this.handleClickOnLikeButton.bind(this)}>
    16. {this.state.isLiked ? likedText : unlikedText} ?
    17. </button>
    18. )
    19. }
    20. }

    render 函数可以看出来,组件内部是通过 this.props 的方式获取到组件的参数的,如果 this.props 里面有需要的属性我们就采用相应的属性,没有的话就用默认的属性。

    那么怎么把 props 传进去呢?在使用一个组件的时候,可以把参数放在标签的属性当中,所有的属性都会作为 props 对象的键值

    1. class Index extends Component {
    2. render () {
    3. return (
    4. <div>
    5. <LikeButton likedText='已赞' unlikedText='赞' />
    6. </div>
    7. )
    8. }
    9. }

    就像你在用普通的 HTML 标签的属性一样,可以把参数放在表示组件的标签上,组件内部就可以通过 this.props 来访问到这些配置参数了。

    React.js 小书教程图片

    前面的章节我们说过,JSX 的表达式插入可以在标签属性上使用。所以其实可以把任何类型的数据作为组件的参数,包括字符串、数字、对象、数组、甚至是函数等等。例如现在我们把一个对象传给点赞组件作为参数:

    1. class Index extends Component {
    2. render () {
    3. return (
    4. <div>
    5. <LikeButton wordings={{likedText: '已赞', unlikedText: '赞'}} />
    6. </div>
    7. )
    8. }
    9. }

    现在我们把 likedTextunlikedText 这两个参数封装到一个叫 wordings 的对象参数内,然后传入点赞组件中。大家看到 {{likedText: '已赞', unlikedText: '赞'}} 这样的代码的时候,不要以为是什么新语法。之前讨论过,JSX 的 {} 内可以嵌入任何表达式,{{}} 就是在 {} 内部用对象字面量返回一个对象而已。

    这时候,点赞按钮的内部就要用 this.props.wordings 来获取到到参数了:

    1. class LikeButton extends Component {
    2. constructor () {
    3. super()
    4. this.state = { isLiked: false }
    5. }
    6. handleClickOnLikeButton () {
    7. this.setState({
    8. isLiked: !this.state.isLiked
    9. })
    10. }
    11. render () {
    12. const wordings = this.props.wordings || {
    13. likedText: '取消',
    14. unlikedText: '点赞'
    15. }
    16. return (
    17. <button onClick={this.handleClickOnLikeButton.bind(this)}>
    18. {this.state.isLiked ? wordings.likedText : wordings.unlikedText} ?
    19. </button>
    20. )
    21. }
    22. }

    甚至可以往组件内部传入函数作为参数:

    1. class Index extends Component {
    2. render () {
    3. return (
    4. <div>
    5. <LikeButton
    6. wordings={{likedText: '已赞', unlikedText: '赞'}}
    7. onClick={() => console.log('Click on like button!')}/>
    8. </div>
    9. )
    10. }
    11. }

    这样可以通过 this.props.onClick 获取到这个传进去的函数,修改 LikeButton handleClickOnLikeButton 方法:

    1. ...
    2. handleClickOnLikeButton () {
    3. this.setState({
    4. isLiked: !this.state.isLiked
    5. })
    6. if (this.props.onClick) {
    7. this.props.onClick()
    8. }
    9. }
    10. ...

    当每次点击按钮的时候,控制台会显示 Click on like button! 。但这个行为不是点赞组件自己实现的,而是我们传进去的。所以,一个组件的行为、显示形态都可以用 props 来控制,就可以达到很好的可配置性。

    默认配置 defaultProps

    上面的组件默认配置我们是通过 || 操作符来实现。这种需要默认配置的情况在 React.js 中非常常见,所以 React.js 也提供了一种方式 defaultProps,可以方便的做到默认配置。

    1. class LikeButton extends Component {
    2. static defaultProps = {
    3. likedText: '取消',
    4. unlikedText: '点赞'
    5. }
    6. constructor () {
    7. super()
    8. this.state = { isLiked: false }
    9. }
    10. handleClickOnLikeButton () {
    11. this.setState({
    12. isLiked: !this.state.isLiked
    13. })
    14. }
    15. render () {
    16. return (
    17. <button onClick={this.handleClickOnLikeButton.bind(this)}>
    18. {this.state.isLiked
    19. ? this.props.likedText
    20. : this.props.unlikedText} ?
    21. </button>
    22. )
    23. }
    24. }

    注意,我们给点赞组件加上了以下的代码:

    1. static defaultProps = {
    2. likedText: '取消',
    3. unlikedText: '点赞'
    4. }

    defaultProps 作为点赞按钮组件的类属性,里面是对 props 中各个属性的默认配置。这样我们就不需要判断配置属性是否传进来了:如果没有传进来,会直接使用 defaultProps 中的默认属性。 所以可以看到,在 render 函数中,我们会直接使用 this.props 而不需要再做判断。

    props 不可变

    props 一旦传入进来就不能改变。修改上面的例子中的 handleClickOnLikeButton

    1. ...
    2. handleClickOnLikeButton () {
    3. this.props.likedText = '取消'
    4. this.setState({
    5. isLiked: !this.state.isLiked
    6. })
    7. }
    8. ...

    我们尝试在用户点击按钮的时候改变 this.props.likedText ,然后你会看到控制台报错了:

    React.js 小书教程图片

    你不能改变一个组件被渲染的时候传进来的 props。React.js 希望一个组件在输入确定的 props 的时候,能够输出确定的 UI 显示形态。如果 props 渲染过程中可以被修改,那么就会导致这个组件显示形态和行为变得不可预测,这样会可能会给组件使用者带来困惑。

    但这并不意味着由 props 决定的显示形态不能被修改。组件的使用者可以主动地通过重新渲染的方式把新的 props 传入组件当中,这样这个组件中由 props 决定的显示形态也会得到相应的改变。

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

    1. class Index extends Component {
    2. constructor () {
    3. super()
    4. this.state = {
    5. likedText: '已赞',
    6. unlikedText: '赞'
    7. }
    8. }
    9. handleClickOnChange () {
    10. this.setState({
    11. likedText: '取消',
    12. unlikedText: '点赞'
    13. })
    14. }
    15. render () {
    16. return (
    17. <div>
    18. <LikeButton
    19. likedText={this.state.likedText}
    20. unlikedText={this.state.unlikedText} />
    21. <div>
    22. <button onClick={this.handleClickOnChange.bind(this)}>
    23. 修改 wordings
    24. </button>
    25. </div>
    26. </div>
    27. )
    28. }
    29. }

    在这里,我们把 Indexstate 中的 likedTextunlikedText 传给 LikeButtonIndex 还有另外一个按钮,点击这个按钮会通过 setState 修改 Indexstate 中的两个属性。

    由于 setState 会导致 Index 重新渲染,所以 LikedButton 会接收到新的 props,并且重新渲染,于是它的显示形态也会得到更新。这就是通过重新渲染的方式来传入新的 props 从而达到修改 LikedButton 显示形态的效果。

    总结

    • 为了使得组件的可定制性更强,在使用组件的时候,可以在标签上加属性来传入配置参数。
    • 组件可以在内部通过 this.props 获取到配置参数,组件可以根据 props 的不同来确定自己的显示形态,达到可配置的效果。
    • 可以通过给组件添加类属性 defaultProps 来配置默认参数。
    • props 一旦传入,你就不可以在组件内部对它进行修改。但是你可以通过父组件主动重新渲染的方式来传入新的 props,从而达到更新的效果。

    课后练习

    • 打开和关闭电脑

    因为第三方评论工具有问题,对本章节有任何疑问的朋友可以移步到 React.js 小书的论坛 发帖,我会回答大家的疑问。