# Counter计数器组件
通过计数器组件来实现数字增加和减少。
## 涉及知识点
>绑定事件,this指向解决,生命周期。内容量还是比较多的
## 新建Counter组件
`src/components/Counter.js`
~~~jsx
import React, {Component} from 'react'
export default class Counter extends Component {
constructor(props) {
super(props)
this.state = {num: 0}
}
icrement() {
this.setState({
num: this.state.num + 1
})
}
render() {
return <div>
<span id="span">{this.state.num}</span>
<br/>
<button onClick={this.icrement.bind(this)}>增加</button>
</div>
}
}
~~~
# 问题
1.在这里面要注意数据更新必须是通过**this.setState()**来进行更新数据,
不可以直接对数据进行this.state.num++这种前置++或者后置++来实现效果。因为this.state.num++只会影响数据增加,但不会更新视图。
this.setState()其实和微信小程序的操作方式很相似。
2.绑定事件,绑定事件要注意,这里要通过onClick= {this.increment}这样写会报错,this指向有问题。打印后this是undefined。必须手动修改this指向。通过this.increment.bind(this);
为什么不是call 和applay,而是bind。答案很简单,call和applay调用的话会立即执行。而bind 不会立即执行它是call和applay的衍生版。
下面是bind的简易版实现方式。
~~~
function bind(fn,context){
return function(){
return fn.apply(context,arguments)
}
}
~~~
如果想看复杂版请查看[Bind高级版](https://www.jianshu.com/p/4b293581a03f)
# 正式进入生命周期
## 初始化阶段
### `constructor`初始化数据
~~~
console.log('这是constructor')
console.log(document.getElementById('box'))
this.setState({
num:1
})
~~~
打印结果
```
这是constructor
null
```
通过打印结果说明网页初始化发布时,默认会先执行constructor生命周期,在这时候还不能获取到真实的dom元素。
### `componentWillMount`将要被挂载
~~~
componentWillMount() {
console.log('这是componentWillMount',2)
console.log(document.getElementById('box'))
}
~~~
打印结果
```
这是componentWillMount 2
null
```
通过打印结果说明网页初始化发布时,第二个执行componentWillMount生命周期,这时候还不能获取到真实的dom元素。
### render生命周期 进行渲染
因为初始化,它会先将数据和结构进行绑定,然后转换为虚拟dom,此时不需要domdiff算法,直接将数据呈现在网页中即可。
~~~jsx
render() {
console.log('这是render',3)
console.log(document.getElementById('box'))
return <div id="box">
<span id="span">{this.state.num}</span>
<br/>
<button onClick={this.icrement}>增加</button>
</div>
}
~~~
打印结果
```
这是render 3
null
```
通过打印结果说明网页初始化发布时,第三个执行render 生命周期,这时候还不能获取到真实的dom元素。
### `componentDidMount`挂载完成
~~~
componentDidMount() {
console.log('这是componentDidMount',4)
console.log(document.getElementById('box'))
}
~~~
```
这是componentDidMount 4
<div id="box">…</div>
```
通过打印结果说明,网页初始化发布是,第四个执行componentDidMount 生命周期,这时候数据和模板已经更新完成,可以获取真正的元素了。
#初始化生命周期小结
通过上面我们已经可以得到什么时候可以去获取真实的dom元素以及它们的执行顺序。
# 运行中生命周期
运行中生命周期分两种情况,可以根据下图来说明,第一种是私有数据发生改变,第二种是属性发生改变。
![](https://box.kancloud.cn/1c14d8a7a85b626eccf1994765579a5b_1611x909.png)
# 我们先研究私有数据即state发生改变的情况。
## shouldComponentUpdate 组件是否更新生命周期
~~~
shouldComponentUpdate(nextProps, nextState, nextContext) {
console.log('shouldComponentUpdate',5)
console.log(document.getElementById('box').innerHTML)
}
~~~
单击增加按钮,我们让state中的num值变为10,控制台会输出下面内容。
```
shouldComponentUpdate 5
<span id="span">0</span><br><button>增加</button>
Warning: Counter.shouldComponentUpdate(): Returned undefined instead of a boolean value. Make sure to return true or false.
```
控制台输出说明,它默认不会自动触发,当私有数据发生变化时才会更新,同样的也能获取到标签元素,不过注意这个标签元素是老的数据,并不是最新的。
我们也发现浏览器中并未将数据更新为10。控制台还报错了:我们必须在这个生命周期中返回true或者false。返回true表示会更新,如果返回false则不更新。
可以尝试自己写写试试效果
### 小结
`shouldComponentUpdate`生命周期,一般我们可以用来优化性能,需要人为来处理,一般对于基础数据我们可以来进行性能优化,如果是比较复杂的对象或者数组则不需要,让它在`render`中进行优化。
## componentWillUpdate
~~~
componentWillUpdate(nextProps, nextState, nextContext) {
console.log('componentWillUpdate',6)
console.log(document.getElementById('box').innerHTML)
}
~~~
控制台输出
```
componentWillUpdate 6
<span id="span">0</span><br><button>增加</button>
```
根据控制台输出结果我们可以看到组件将要被更新,且能获取到元素,且元素的内容还是老旧内容。
## render生命周期函数
render生命周期函数改造
在此时,这个生命周期函数做的事情较为复杂,内部需要对比老旧的虚拟dom,进行diff算法。
~~~
console.log('这是render',3)
console.log(document.getElementById('box')&&document.getElementById('box').innerHTML)
~~~
控制台输出
```
这是render 3
Counter.js:65 <span id="span">0</span><br><button>增加</button>
```
根据控制台输出,表明render生命周期还是之前那一个,同样的,它获取的元素内容还是之前老旧的内容。
## componentDidUpdate生命周期
~~~
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('componentDidUpdate',7)
console.log(document.getElementById('box').innerHTML)
}
~~~
控制台输出
```
componentDidUpdate 7
<span id="span">1</span><br><button>增加</button>
```
这时我们它是运行中生命周期中最后一个,同时,完成更新后,它获取的元素内容已经成为最新的了。
## 运行中生命周期小结
运行中生命周期中是组件持续时间最长的。只要触发this.setState这个函数它就会执行。
大家可以猜想一下为什么在初始化render生命周期不可以调用this.setState可以直接告诉大家它是死循环,可以将你的思考总结一下(了解一下)**,这个题需要看完思考题 再来解释。**
# 组件销毁生命周期(了解掌握)
组件销毁生命周期这个案例比较复杂,在理解上有点麻烦。大家刚开始时候只需要能跟着写就可以了。在这里我们需要再学习两个ReactDOM的api。
通过ReactDOM的unmountComponentAtNode来卸载组件。不过这里卸载组件只能卸载通过ReactDOM.render加载的组件。
~~~
ReactDom.unmountComponentAtNode(document.getElementById('box'))
销毁指定容器内的所有React节点。
~~~
我们需要结合Counter计数组器组件和Main.js中添加一层组件Life组件。
## 新建Life.js组件
~~~
import React, {Component, Fragment} from 'react'
import ReactDom from 'react-dom'
import Counter from "./Counter";
export default class Life extends Component{
unmont(){
ReactDom.unmountComponentAtNode(document.getElementById('mybox'))
}
render() {
return (
<Fragment>
<button onClick={this.unmont.bind(this)}>卸载</button>
<div id="mybox">
<Counter/>
</div>
</Fragment>
)
}
}
~~~
### 修改main.js
~~~
ReactDom.render(<Life/>,document.getElementById('root'))
~~~
![](https://box.kancloud.cn/d8350225367478bda6a33091e05703ec_521x97.png)
##尝试卸载
单击卸载按钮报错
```
Warning: unmountComponentAtNode(): The node you're attempting to unmount was rendered by React and is not a top-level container. You may have accidentally passed in a React root node instead of its container.
```
报错信息,说的不通过这种形式卸载组件。
## 解决问题
我们还记得可以卸载的组件只能通过通过ReactDOM.render()的组件才可以卸载掉。
~~~
unmont(){
ReactDom.unmountComponentAtNode(document.getElementById('mybox'))
}
mount(){
ReactDom.render(<Counter/>,document.getElementById('mybox'))
}
render() {
return (
<Fragment>
<button onClick={this.mount.bind(this)}>加载</button>
<button onClick={this.unmont.bind(this)}>卸载</button>
<div id="mybox">
</div>
</Fragment>
)
}
~~~
通过上面,我们要通过单击加载按钮才可以让元素显示出来,同样的我们单击卸载,它也可以直接将Counter组件卸载掉。
我们设置Counter.js组件中的卸载生命周期
~~~
componentWillUnmount() {
console.log('componentWillUnmount',8);
console.log(document.getElementById('box').innerHTML)
}
~~~
单击卸载按钮,控制台输出
```
componentWillUnmount 8
Counter.js:111 <span id="span">0</span><br><button>增加</button>
```
组件卸载生命周期只有一个,但是也比较重要,比如当前页面被卸载掉后我们经常要使用它来清除定时器,以达到节省性能的效果。
# 运行中生命周期第二种情况
运行中生命周期第二种情况就是当父组件给子组件传递数据,引发的改变。
那么LIfe组件中使用了Counter组件,那么在Life组件中我们可以去改变Counter组件的数据。
## Life组件
~~~
import React, {Component, Fragment} from 'react'
import ReactDom from 'react-dom'
import Counter from "./Counter";
export default class Life extends Component{
constructor(props){
super(props);
this.state = {
number:10
}
}
unmont(){
ReactDom.unmountComponentAtNode(document.getElementById('mybox'))
}
mount(){
ReactDom.render(<Counter number={this.state.number}/>,document.getElementById('mybox'))
}
changeState(){
this.setState({
number:100
},()=>{
this.mount();
})
}
render() {
return (
<Fragment>
<button onClick={this.changeState.bind(this)}>修改number</button>
<button onClick={this.mount.bind(this)}>加载</button>
<button onClick={this.unmont.bind(this)}>卸载</button>
<div id="mybox">
</div>
</Fragment>
)
}
}
~~~
## Countr组件
~~~
constructor(props) {
super(props)
this.state = {num: props.number}
console.log('这是constructor,1')
console.log(document.getElementById('box'))
}
componentWillReceiveProps(nextProps, nextContext) {
console.log('componentWillReceiveProps',9)
console.log(document.getElementById('box').innerHTML)
}
~~~
## 解释
在这里面我们在Life组件中新添加了私有数据,然后级Counter组件的父组件传递number数据,让子组件接收props,然后将props的数据放到Counter组件的state数据的num属性中
### 单击加载,再单击修改number
控制台输出结果
```
componentWillReceiveProps 9
<span id="span">10</span><br><button>增加</button>
shouldComponentUpdate 5
<span id="span">10</span><br><button>增加</button>
componentWillUpdate 6
<span id="span">10</span><br><button>增加</button>
这是render 3
<span id="span">10</span><br><button>增加</button>
componentDidUpdate 7
<span id="span">10</span><br><button>增加</button>
```
我们可以看出只要当父组件的属性的值发生变化 ,那么会引起子组件运行中的所有生命周期会一一执行。不过还可以发现一个问题,我们更新父组件中的number值,子组件并没有更新成功,原因是因为什么呢?
### 解决
componentWillReceiveProps 这个生命周期我们可以接收到父容器的数据,我们来查看一下。
其实我们应该利用最新的属性数据来改变私有数据。让其更新。基本新的私有数据如何获得呢?我们通过控制台发现props并不是最新的数据,而nextProps才是最新的数据
~~~
componentWillReceiveProps(nextProps, nextContext) {
console.log(this.props.number)
console.log(nextProps.number)
console.log('componentWillReceiveProps',9)
console.log(document.getElementById('box').innerHTML)
}
~~~
![](https://box.kancloud.cn/837b6c50ddab4a255d6f4c0318153ab2_1215x337.png)
和最新的props数据来更新state中的数据
~~~
componentWillReceiveProps(nextProps, nextContext) {
console.log(this.props.number)
console.log(nextProps.number)
this.setState({
num:nextProps.number
})
console.log('componentWillReceiveProps',9)
console.log(document.getElementById('box').innerHTML)
}
~~~
![](https://box.kancloud.cn/3e97cf36be81b57489ee8ccf91039cc4_1027x314.png)
## 思考题:
但是他们什么时候可以去更新数据呢?也就是this.setState这个函数在生命周期中什么时候可以执行呢?即比如ajax呢?
我们可以在上面的所有生命周期中,都添加`this.setState({num:10})`。
发现如下效果:
### constructor中不能更新数据
~~~
Warning: Can't call setState on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to `this.state` directly or define a `state = {};` class property with the desired state in the Counter component
~~~
>警告:无法对尚未挂载的组件调用SetState。这是一个禁止操作,但它可能指示应用程序中的错误。相反,直接分配给“this.state”,或在计数器组件中定义一个具有所需状态的“state=”类属性。
### render,shouldComponentUpdate,componentWillUpdate,componentDidUpdate不能更新数据错误原因一样
~~~
Uncaught Invariant Violation: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
~~~
超出最大更新深度。当组件在componentWillUpdate或componentDidUpdate中重复调用setState时,可能会发生这种情况。React限制嵌套更新的数量以防止无限循环。
原因是为什么?因为当state发生改变会执行上面的生命周期,自然而然的就导致死循环,最终报错。
### componentWillMount,componentDidMount,componentWillReceiveProps可以更新数据
这里面可以进行ajax。
![](https://box.kancloud.cn/a60c788caa3764ca3cf4ef131068743a_898x518.png)
总结:React生命周期在学习上成本上是很大的,在刚工始学习过程中会遇到问题,也会感觉到特别麻烦 ,反而没有Vue的生命周期好用。这些都是正常的,要多练习几次就可以达到熟练的效果。
- webpack复习
- React基础
- 前端三大主流框架对比
- React中几个核心的概念
- React基础语法
- React JSX语法
- React组件
- 普通组件
- 组件通信-父向子传递
- 组件拆成单个文件
- 面向对象复习
- Class组件基础
- Class组件的私有状态(私有数据)
- 案例:实现评论列表组件
- 组件样式管理
- 组件样式分离-样式表
- CSS模块化
- 生命周期
- React组件生命周期
- Counter组件来学习组件生命周期
- 生命周期总结
- 生命周期案例
- React评论列表
- React双向数据绑定
- React版todolist
- 其它提高(了解)
- 组件默认值和数据类型验证
- 绑定this并传参的三种方式
- 祖孙级和非父子组件传递数据(了解)
- React路由
- 路由基础
- 动态路由
- 路由严格模式
- 路由导航定位
- 路由重定向
- 路由懒加载
- WolfMovie项目
- 项目初始化
- AntDesign使用
- 其它相关了解