react学习记录-挂载阶段的组件生命周期

挂载阶段的组件生命周期

之前提到过我们定义的组件实际上等价于React.createElement 函数,它会将返回结果传给 ReactDOM.render。过程如下:

// <Header /> ===> React.createElement(Header, null)
// React.createElement 中实例化一个 Header
const header = new Header(props, children)
// React.createElement 中调用 header.render 方法渲染组件的内容
const headerJsxObject = header.render()

// ReactDOM 用渲染后的 JavaScript 对象来来构建真正的 DOM 元素
const headerDOM = createDOMFromObject(headerJsxObject)
// ReactDOM 把 DOM 元素塞到页面上
document.getElementById('root').appendChild(headerDOM)

我们把 React.js 将组件渲染,并且构造 DOM 元素然后塞入页面的过程称为组件的挂载。每个组件都会经过初始化 -> 挂载到页面上 这个过程。

可以理解一个组件的方法调用是这么一个过程:

-> constructor()
-> render()
// 然后构造 DOM 元素插入页面

constructor()、componentWillMount()、componentDidMount()

React.js 为了让我们能够更好的掌控组件的挂载过程,往上面插入了两个方法。在创建组件实例并将其插入DOM时,将按以下顺序调用这些方法:

这里我们默认还是使用 componentWillMount,并编写一段Header组件:

class Header extends Component {
  constructor () {
    super()
    console.log('construct')
  }

  componentWillMount () {
    console.log('component will mount')
  }

  componentDidMount () {
    console.log('component did mount')
  }

  render () {
    console.log('render')
    return (
      <div>
        <h1 className='title'>React 小书</h1>
      </div>
    )
  }
}

我们可以在控制台看到它们按照挂载过程被先后打印(严格模式下constructor和render会执行两次)。

componentWillUnmount()

有挂载,也就有删除:

-> constructor()
-> componentWillMount()
-> render()
// 然后构造 DOM 元素插入页面
-> componentDidMount()
// ...
// 即将从页面中删除
-> componentWillUnmount()
// 从页面中删除

我们可以用下面的例子来演示组件的unmount,点击按钮,隐藏Header组件时,就会打印卸载:

class Header extends Component {
  ...
  componentWillUnmount() {
    console.log('component will unmount')
  }
  ...
}
class Index extends Component {
  constructor() {
    super()
    this.state = {
      isShowHeader: true
    }
  }

  handleShowOrHide () {
    this.setState({
      isShowHeader: !this.state.isShowHeader
    })
  }

  render () {
    return (
      <div>
        {this.state.isShowHeader ? <Header /> : null}
        <button onClick={this.handleShowOrHide.bind(this)}>
          显示或者隐藏标题
        </button>
      </div>
    )
  }
}

ReactDOM.render(
  <Index />,
  document.getElementById('root')
)

各个生命周期函数的具体使用场合

我们来讨论一下上面提到的四个组件生命周期函数constructorcomponentWillMountcomponentDidMountcomponentWillUnmount,它们贯穿了组件的出生到死亡,以一个时钟应用为例子,来丰富这四个生命周期函数。

首先,state的初始化是在constructor 中完成的,如下:

class Clock extends Component {
  constructor () {
    super()
    this.state = {
      date: new Date()
    }
  }

  render () {
    return (
      <div>
        <h1>
          <p>现在的时间是</p>
          {this.state.date.toLocaleTimeString()}
        </h1>
      </div>
    )
  }
}

一些组件启动的操作,比如说 Ajax 数据的拉取操作、一些定时器的启动等,就可以放在 componentWillMount 里面进行,例如 Ajax:

...
  componentWillMount () {
    ajax.get('http://json-api.com/user', (userData) => {
      this.setState({ userData })
    })
  }
...

当然在我们这个例子里面是定时器的启动,我们给 Clock 启动定时器:

class Clock extends Component {
  constructor () {
    super()
    this.state = {
      date: new Date()
    }
  }

  componentWillMount () {
    this.timer = setInterval(() => {
      this.setState({ date: new Date() })
    }, 1000)
  }
  ...
}

现在每隔一秒,闹钟的时间就会更新,现在让Index组件去使用Clock组件,并插入页面。我们再增加一个按钮,去使得时钟可以隐藏或者显示:

class Index extends Component {
  constructor () {
    super()
    this.state = { isShowClock: true }
  }

  handleShowOrHide () {
    this.setState({
      isShowClock: !this.state.isShowClock
    })
  }

  render () {
    return (
      <div>
        {this.state.isShowClock ? <Clock /> : null }
        <button onClick={this.handleShowOrHide.bind(this)}>
          显示或隐藏时钟
        </button>
      </div>
    )
  }
}

当我们选择隐藏闹钟组件时,会出现一个情况:

在这里插入图片描述

这是因为我们没有在闹钟隐藏之后消除定时器。时钟隐藏的时候,定时器的回调函数还在不停地尝试 setState,由于 setState 只能在已经挂载或者正在挂载的组件上调用,所以 React.js 开始疯狂报错。

多次的隐藏和显示会让 React.js 重新构造和销毁 Clock 组件,每次构造都会重新构建一个定时器。而销毁组件的时候没有清除定时器,所以你看到报错会越来越多。而且因为 JavaScript 的闭包特性,这样会导致严重的内存泄漏。

这时候componentWillUnmount 就可以派上用场了,它的作用就是在组件销毁的时候,做这种清场的工作。

...
  componentWillUnmount () {
    clearInterval(this.timer)
  }
...

这时候就没有错误了。

至于 componentDidMount ,一般来说,有些组件的启动工作是依赖 DOM 的,例如动画的启动,而 componentWillMount 的时候组件还没挂载完成,所以没法进行这些启动工作,这时候就可以把这些操作放在 componentDidMount 当中。

课后习题

class Post extends Component {
  constructor(props){
    super(props)
    this.state={
      showContent:"数据加载中..."
    }
  }
  componentWillMount(){
    this.setState({
      showContent:"数据加载中..."
    })
    getPostData().then((postContent) => {
      this.setState({
        showContent:postContent
      })
    })
  }
  render () {
    return (
      <div>
        <div className='post-content'>{this.state.showContent}</div>
        <button onClick={this.componentWillMount.bind(this)}>刷新</button>
      </div>
    )
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值