今天看啥  ›  专栏  ›  侠客有情剑无情

React从0到1系列第三章——深入理解React组件的组合、props和state

侠客有情剑无情  · 掘金  ·  · 2020-06-20 12:31
阅读 56

React从0到1系列第三章——深入理解React组件的组合、props和state

一、组件的组和、组件树

想要理解数据是如何在组件树内自上往下流动(Data Flow),需要先明白React组件的组合、嵌套和组件树的构成。

注意:自定义的组件都必须要用大写字母开头,普通的HTML标签都用小写字母开头。

下面是用React.js组建的房子为例子来说明一下组件的组和应用。一个房子里面有一个房间和一个厕所。房间里有一台电脑和两条狗,如下:

// 电脑
class Computer extends Component {
  render () {
    return <h3>this is Computer</h3>
  }
}

// 狗
class Dog extends Component {
  render () {
    return <h3>this is Dog</h3>
  }
}

// 房间
class Room extends Component {
  render () {
    return (
      <div>
        <h2>this is Room</h2>
        <Computer />
        <Dog />
        <Dog />
      </div>
    )
  }
}

// 厕所
class WC extends Component {
  render () {
    return <h2>this is WC</h2>
  }
}

// 房子
class House extends Component {
  render () {
    return <div>
      <h1>this is House</h1>
      <Room />
      <WC />
    </div>
  }
}
复制代码

最后页面会输出如下图:

组件可以和组件组合在一起,组件内部可以使用别的组件。这样的组合嵌套,最后构成一个所谓的组件树。用下图表示这种树状结构它们之间的关系:

二、组件的state

state是组件的当前状态,React根据状态state呈现不同的UI展示。React.jsstate 就是用来存储这种可变化的状态的。一旦状态(数据)更改,组件就会自动调用render重新渲染 UI,这个更改的动作通过this.setState()方法来触发。

下面是一个简单的修改状态的小例子:

class Like extends Component {
  constructor(props) {
    super(props)
    this.state = { isLike: false }
  }
  handleClick () {
    this.setState({
      isLike: !this.state.isLike
    })
  }
  render () {
    return (
      <h3 onClick={this.handleClick.bind(this)}>
        你{this.state.isLike ? '喜欢' : '不喜欢'}她
      </h3>
    )
  }
}
复制代码

isLike存放在实例的state 对象当中,这个对象在构造函数里面初始化。在这个组件的render函数内,会根据组件的state 的中的isLike的不同显示喜欢或不喜欢。setState()方法由父类Component所提供。

正确的使用statesetState()方法

setState()方法,它接受一个对象或者函数作为参数。

  • 对象作为参数时
handleClick () {
  // 只需要传入需要更新的部分,而不需要入整个对象。
  this.setState({
    isLike: !this.state.isLike
  })
}
复制代码
  • 函数作为参数时
handleClick () {
  this.setState(() => {
    return { isLike: !this.state.isLike }
  })
}
复制代码

不能直接修改state

不能直接修改state,如果直接修改state,组件并不会重新触发render方法。

// 错误
this.state.isLike = !this.state.isLike

// 正确
this.setState({
  isLike: !this.state.isLike
})
复制代码

state的更新是异步的

在调用setState()的时候React.js并不会马上修改statesetState()只是把要修改的状态放入一个更新队列中。React会优化真正的执行时机。如下:

class AddCount extends Component {
  constructor(props) {
    super(props)
    this.state = { count: 0 }
  }
  handleClick () {
    this.setState({ count: this.state.count + 1 })
    // 0
    console.log(this.state.count)
  }
  render () {
    return (
      <div>
        {/* 1 */}
        <h2>{this.state.count}</h2>
        <button onClick={this.handleClick.bind(this)}>添加</button>
      </div>
    )
  }
}

复制代码

当点击添加按钮的时候页面输出的是1,打印出来的却是0。所以在调用setState()之后,this.state不会立即映射为新的值。

解决方案是setState(updater, [callback])接收更新后可以传入一个回调函数,一旦setState()完成并且组件重绘之后,这个回调函数将会被调用。

handleClick () {
  this.setState({ count: this.state.count + 1 }, () => {
    // 1
    console.log(this.state.count)
  })
}
复制代码

setState()浅合并

React.js出于性能原因,可能会将多次setState()的状态修改合并成一次状态修改。所以不要依赖当前的setState()计算下个State。如下的一个计数器:

class AddCount extends Component {
  constructor(props) {
    super(props)
    this.state = { count: 0 }
  }
  handleClick () {
    this.setState({ count: this.state.count + 1 })
    this.setState({ count: this.state.count + 1 })
  }
  render () {
    return (
      <div>
        <h2>{this.state.count}</h2>
        <button onClick={this.handleClick.bind(this)}>添加</button>
      </div>
    )
  }
}
复制代码

当点击添加按钮的时候,会发现页面输出的是1。虽然我们setState()方法调用了两次。是因为当我们调用setState()修改组件状态时,组件state的更新其实是一个浅合并的过程,相当于:

Object.assign(
  previousState,
  {count: state.count + 1},
  {count: state.count + 1},
  ...
)

复制代码

所以如果后续操作要依赖前一个setState()的结果的情况下就要使用函数来作为setState()参数。React.js会把上一个setState()的结果传入这个函数,你就可以使用上一个正确的结果进行操作,然后返回一个对象来更新state。

handleClick () {
  this.setState({ count: this.state.count + 1 })
  this.setState((prevState) => {
    // 1
    console.log(prevState.count)
    return { count: prevState.count + 1 }
  })
}}
复制代码

把上次操作setState()更新的值传入到下一个setState()里,就可以正确的显示count了。

stateImmutable(不可变性)

React官方建议把state当作是的Immutable(不可变性)对象,state中包含的所有状态都应该是不可变对象。当state中的某个状态发生变化,我们应该重新创建这个状态对象,而不是直接修改原来的状态。

关于Immutable可以参考Immutable详解及React中实践

  1. 如有一个数组类型的状态names,当向name中添加一个名字时,使用数组的concat方法或ES6的扩展运算符:
// 1
this.setState(prevState => ({
  names: prevState.names.concat(['lisi'])
}))

// 2
this.setState(prevState => ({
  names: [...prevState.names,'lisi']
}))

复制代码

注意不要使用pushpopshiftunshiftsplice等方法修改数组类型的状态,因为这些方法都是在原数组的基础上修改,而concatslice等返回一个新的数组。

  1. 如有一个对象类型的状态person,为了不改变原本的对象,我们可以使用Object.assign 方法或者对象扩展属性:
// 1
function updatePerson (person) {
  return Object.assign({}, person, { age: 20 })
}

// 2
function updatePerson (person) {
  return {...person,age:20}
}

复制代码

创建新的状态对象要避免使用会直接修改原对象的方法,而是使用可以返回一个新对象的方法。

当处理深层嵌套对象时,以immutable(不可变)的方式更新会很麻烦。可以使用一些Immutable的JS库,如Immutable.js来简化开发。

三、组件的props

组件是相互独立、可复用的单元,一个组件可能在不同地方被用到。但是在不同的场景下对这个组件的需求可能会根据情况有所不同,所以要针对相同的组件传入不同的配置项。

React.jsprops就可以帮助我们达到这个效果。每个组件都可以接受一个props参数,它是一个对象,包含了所有你对这个组件的配置。

下面是一个可以通过传入props来控制是不是可以点击修改喜欢或不喜欢的文字:

class Like extends Component {
  constructor(props) {
    super(props)
    this.state = { isLike: false }
  }
  handleClick () {
    this.setState({
      isLike: !this.state.isLike
    })
  }
  render () {
    const clickable = this.props.clickable
    return (
      <h3 onClick={clickable ? this.handleClick.bind(this) : () => { }} >
        你{ this.state.isLike ? '喜欢' : '不喜欢'}她
      </h3>
    )
  }
}

class App extends Component {

  render () {
    return (
      <div>
        <Like clickable={true} />
        <Like clickable={false} />
      </div>
    )
  }
}
复制代码

组件内部是通过this.props的方式获取到组件的参数的,如果this.props里面有需要的属性我们就采用相应的属性,没有的话就用默认的属性。在使用一个组件的时候,可以把参数放在标签的属性当中,所有的属性都会作为props`对象的键值。

默认配置defaultProps

React.js也提供了一种方式defaultProps,可以方便的做到默认配置。

class Like extends Component {
 static defaultProps = {
   clickable: true
 }
 //...
}
复制代码

props 不可变

props一旦传入进来就不可以在组件内部对它进行修改。但是可以通过父组件主动重新渲染的方式来传入新的props,从而达到更新的效果。如下:

class App extends Component {

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

  handleClickOnChange () {
    this.setState({
      clickable: !this.state.clickable
    })
  }
  render () {
    return (
      <div>
        <Like clickable={this.state.clickable} />
        <Like clickable={this.state.clickable} />
        <button onClick={this.handleClickOnChange.bind(this)}>
          修改clickable
          </button>
      </div>
    )
  }

复制代码

参考

https://zh-hans.reactjs.org/

https://juejin.im/entry/59522bdb6fb9a06b9a516113

https://github.com/camsong/blog/issues/3

https://github.com/lcxfs1991/blog/issues/8

http://huziketang.mangojuice.top/books/react/

https://dev.to/rleija_/master-the-art-of-react-state-and-props-in-5-minutes-3hao

http://product.dangdang.com/25249546.html

本文使用 mdnice 排版




原文地址:访问原文地址
快照地址: 访问文章快照