函数式组件只有属性,没有状态,更没有生命周期的概念,但是函数式组件在React16.8引入hooks以后,可以在函数式组件里使用state和生命周期等特性,拥有类似于类组件的能力,同时保持简洁和易用性。
在生命周期的某个阶段会触发的东西
写不写本身不会影响组件的执行,但是写的话会在生命周期的某个阶段触发
整个生命中周期分为三个阶段:初始化阶段、运行阶段、销毁阶段
初始化阶段
执行顺序:WillMount=>render=>DidMount
javascript
import React, { Component } from 'react';
class App extends Component {
state={
myname:'荷叶饭'
}
componentWillMount() {
console.log('WillMount', this.state.myname, document.getElementById('myname'))
//WillUnmount,将要挂载到组件中,只会执行一次,上树(上dom树)前的最后一次修改状态的机会,但是拿不到dom
//简单说就说初始化数据的作用
this.setState({
myname:'励志轩'
})
}
componentDidMount() {
console.log('DidMount',document.getElementById('myname'))
//DidMount,已经挂载到组件中,只会执行一次
//一般放置数据请求,axios,ajax,订阅函数的调用,setInterval
//基于创建完的dom进行初始化,BetterScroll就说基于创建完的dom来让长列表滚动
}
render() {
console.log('render')
// 写完以后会自动执行
return (
//render里不能直接修改状态,所以WillMount是上树前的最后一次修改状态的机会
<div>
<span id='myname'>{ this.state.myname}</span>
</div>
);
}
}
export default App;
React中的生命周期函数,也叫做钩子函数,钩子函数起到的作用是被咬钩的时候我知道了
这里报警告是因为WillMount 生命周期方法,该方法在 React 16.3 之后已被标记为 不安全的(unsafe),所以上面写的东西只适用于React16.2以前
我真是服了缝缝补补的前端
React版本的重要节点:16.2 之前都是老版本的生命周期函数,使用的时候可以自己将其重命名为UNSAFE_componentWillMount,相当于自己提醒自己
警告消失了:
因为在16.2以后优化了diff(新的对比旧的)算法,提出了Fiber(纤维,新协调算法,支持异步渲染、渲染优先级调度),WillMount就属于进行diff算法的部分,为第一优先级,以前来说要等它执行完毕,但是根据新的机制会打断第一优先级,所以在后期的使用就有了安全隐患
函数组件在 16.8之前也是没有生命周期的
用我们刚学的componentDidMount来做数据请求
WillMount应用实例
javascript
import React, { Component } from 'react';
import BetterScroll from 'better-scroll'
class App extends Component {
state = {
list:['111','222','333','444','555','666','777','888','999','121']
}
UNSAFE_componentWillMount() {
//获取不了dom节点
}
componentDidMount() {
//可以获取dom节点
new BetterScroll('#wrapper')
}
render() {
return (
<div>
<div id='wrapper' style={{background:'yellow',height:'200px',overflow:'hidden'}}>
<ul>
{
this.state.list.map(item =>
<li key={item}>{item}</li>
)
}
</ul>
</div>
</div>
);
}
}
export default App;
运行中阶段
看看他们render、componentDidUpdate、componentWillUpdate三个的执行顺序:
javascript
import React, { Component } from 'react';
class App extends Component {
state = {
myname:'kerwin'
}
render() {
console.log('render')
return (
<div>
<button onClick={() => {
this.setState({
myname:'tiechui'
})
}}>click</button>
<span id='myname'> {this.state.myname}</span>
</div>
)
}
UNSAFE_componentWillUpdate() {
console.log('componentWillUpdate',document.getElementById('myname').innerHTML)
}
componentDidUpdate() {
console.log('componentDidUpdate',document.getElementById('myname').innerHTML)
}
}
export default App;
render=>点击按钮=>componentWillUpdate=>render=>componentDidUpdate
先渲染=>点击按钮以后=>准备更新=>正在更新=>更新完成
每次更新完dom的时候new一个BetterScroll,加上if判断可以防止new多次的BetterScroll
javascript
import React, { Component } from 'react';
import BetterScroll from 'better-scroll'
import axios from 'axios'
class App extends Component {
state = {
myname: 'kerwin',
list:[]
}
componentDidMount() {
axios.get('/test.json').then(res => {
console.log(res.data.data.films)
this.setState({
list:res.data.data.films
},
)
})
}
render() {
console.log('render')
return (
<div>
<button onClick={() => {
this.setState({
myname:'tiechui'
})
}}>click</button>
<span id='myname'> {this.state.myname}</span>
<div id='wrapper' style={{height:'100px',overflow:'hidden',background:'pink'}}><ul>
{
this.state.list.map(item => <li key={item.filmId}>{item.name}</li>)
}
</ul></div>
</div>
)
}
UNSAFE_componentWillUpdate() {
console.log('componentWillUpdate',document.getElementById('myname').innerHTML)
}
componentDidUpdate(prevProps,prevState) {
console.log('componentDidUpdate', document.getElementById('myname').innerHTML)
if (prevState.list.length === 0) {
new BetterScroll('#wrapper')
}
}
}
export default App;
但是如果只有这个三个普通的生命周期函数,那么只要点击按钮,即使state并没有变化,也会调用render函数

SCU
这时候就需要shouldComponentUpdate,这个函数简称SCU,性能优化函数(同理CDM是componentDidMount,),来判断组件是否应该更新:
javascript
shouldComponentUpdate(nextProps,nextState) {
//return true//应该更新
//return false//阻止更新
//做条件判断:
if (this.state.myname!==nextState.myname) {
return true
}
return false
}
如果要对比的内容是对象,可以转化为json字符串对比:
SCU优化性能的案例:
javascript
import React, { Component } from 'react';
class Box extends Component{
//优化思路:只拿改变的div和前一个做对比就好了
shouldComponentUpdate(nextProps) {
if (this.props.current===this.props.index||nextProps.current===nextProps.index) {
return true
}
return false
}
render() {
return <div
style={{ width: '100px', height: '100px', border: this.props.current === this.props.index ? '5px solid red' : '5px solid grey', margin: '10px', float: 'left' }}>
</div>
}
}
class App extends Component {
state = {
list: ['00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10',],
current:0
}
render() {
return (
<div>
<input type='number' onChange={(e) => {
console.log(e.target.value)
this.setState({
current:Number(e.target.value)
})
}} />
<div style={{overflow:'hidden'} }>
{this.state.list.map((item, index) => <Box key={item} current={this.state.current} index={index} />)}
</div>
</div>
);
}
}
export default App;
最先获取父组件传来的属性,相当于子组件里的初始化
javascript
import React, { Component } from 'react';
class Child extends Component{
state = {
title:''
}
render() {
return (
<div>
child-{this.state.title}
</div>
)
}
UNSAFE_componentWillReceiveProps(nextProps) {
console.log(' UNSAFE_componentWillReceiveProps',this.props.text)//第一次点击按钮拿到的是老属性,11111111
console.log(' UNSAFE_componentWillReceiveProps', nextProps)//拿到的是新属性,22222222
//该生命周期最先获得父组件传来的属性,可以利用属性进行ajax或者逻辑处理
//把拿到的属性转化为自己孩子的状态
this.setState({
title:nextProps.text+'荷叶饭'
})
}
}
class App extends Component {
state = {
text:'11111111'
}
render() {
return (
<div>
{
this.state.text
}
<button onClick={() => {
this.setState({
text:'22222222'
})
}}>click</button>
<Child text={this.state.text} />
</div>
);
}
}
export default App;
请求到的是老的数据,需要加入形参nextProps

componentWillReceiveProps
外部组件多次频繁更新传入多次不同的 props ,会导致不必要的异步请求
javascript
/*
* @作者: kerwin
* @公众号: 大前端私房菜
*/
import React, { Component } from 'react'
import axios from 'axios'
export default class App extends Component {
state = {
type:1
}
render() {
return (
<div>
<ul>
<li onClick={()=>{
this.setState({
type:1
})
}}>正在热映</li>
<li onClick={()=>{
this.setState({
type:2
})
}}>即将上映</li>
</ul>
<FilmList type={this.state.type}></FilmList>
</div>
)
}
}
class FilmList extends Component{
state = {
list:[]
}
//初始化-执行一次
componentDidMount() {
// console.log(this.props.type)
if(this.props.type===1){
//请求卖座正在热映的数据
console.log("请求卖座正在热映的数据")
axios({
url:"https://m.maizuo.com/gateway?cityId=110100&pageNum=1&pageSize=10&type=1&k=6369301",
headers:{
'X-Client-Info': '{"a":"3000","ch":"1002","v":"5.2.0","e":"16395416565231270166529","bc":"110100"}',
'X-Host': 'mall.film-ticket.film.list'
}
}).then(res=>{
console.log(res.data.data.films)
this.setState({
list:res.data.data.films
})
})
}else{
//请求卖座即将上映的数据
console.log("请求卖座即将上映的数据")
axios({
url:"https://m.maizuo.com/gateway?cityId=110100&pageNum=1&pageSize=10&type=2&k=8077848",
headers:{
'X-Client-Info': '{"a":"3000","ch":"1002","v":"5.2.0","e":"16395416565231270166529","bc":"110100"}',
'X-Host': 'mall.film-ticket.film.list'
}
}).then(res=>{
console.log(res.data.data.films)
this.setState({
list:res.data.data.films
})
})
}
}
UNSAFE_componentWillReceiveProps(nextProps){
if(nextProps.type===1){
//请求卖座正在热映的数据
console.log("请求卖座正在热映的数据")
axios({
url:"https://m.maizuo.com/gateway?cityId=110100&pageNum=1&pageSize=10&type=1&k=6369301",
headers:{
'X-Client-Info': '{"a":"3000","ch":"1002","v":"5.2.0","e":"16395416565231270166529","bc":"110100"}',
'X-Host': 'mall.film-ticket.film.list'
}
}).then(res=>{
console.log(res.data.data.films)
this.setState({
list:res.data.data.films
})
})
}else{
//请求卖座即将上映的数据
console.log("请求卖座即将上映的数据")
axios({
url:"https://m.maizuo.com/gateway?cityId=110100&pageNum=1&pageSize=10&type=2&k=8077848",
headers:{
'X-Client-Info': '{"a":"3000","ch":"1002","v":"5.2.0","e":"16395416565231270166529","bc":"110100"}',
'X-Host': 'mall.film-ticket.film.list'
}
}).then(res=>{
console.log(res.data.data.films)
this.setState({
list:res.data.data.films
})
})
}
}
render(){
return <ul>
{
this.state.list.map(item=>
<li key={item.filmId}>{item.name}</li>
)
}
</ul>
}
}
销毁阶段
例如你在组件里写了一个定时器,即使组件删除了定时器也还在,就会降低性能,这时候就需要在销毁阶段销毁定时器
进行清理操作的生命周期函数
javascript
import { isCancel } from 'axios';
import React, { Component } from 'react';
class App extends Component {
state = {
isCreated:false
}
render() {
return (
<div>
<button onClick={() => {
this.setState({
isCreated:!this.state.isCreated
})
}}>click</button>
{/* {this.state.isCreated?<Child></Child>:''} */}
{this.state.isCreated&&<Child></Child>}
</div>
);
}
}
class Child extends Component {
render() {
return (
<div>
child
</div>
)
}
componentDidMount() {
window.onresize = () => {
console.log('resize')
}
//绑在window上的定时器,如果只卸载组件定时器还会运行
this.timer=setInterval(() => {
console.log('我是定时器')
}, 1000);
}
componentWillUnmount() {
console.log('componentWillUnmount')
//销毁child组件的同时销毁事件监听和定时器
clearInterval(this.timer)
window.onresize=null
}
}
export default App;
新的生命周期的替代
修复旧生命周期函数的警告
getDerivedStateFromProps
初始化的时候代替componentWillMount,在ssr中 这个方法将会被多次调用, 所以会重复触发多遍,同时在这里如果绑定事件, 将无法解绑,导致内存泄漏 , 变得不够安全高效逐步废弃。
在后续更新的时候取代componentWillReceiveProps 外部组件多次频繁更新传入多次不同的 props,会导致不必要的异步请求
该函数涵盖了初始化和更新阶段,适用于这两个阶段都重复执行的逻辑
第一次的初始化组件以及后续的更新过程中(包括自身状态更新以及父传子) ,
返回一个对象作为新的state,返回null则说明不需要在这里更新state
需要静态方法,返回一个对象
把传过来的state.myname转为首字母大写
javascript
import React, { Component } from 'react';
class App extends Component {
state = {
myname: 'kerwin',
myage:19
}
//componentWillMount 初始化
static getDerivedStateFromProps(nextProps,nextState) {
console.log('getDrivedStateFromProps',nextState)
//在这个生命周期内做最后的修改,return的结果会把原来的state对象和返回的对象合并
return {
//实现首字母大写效果
myname:nextState.myname.substring(0,1).toUpperCase()+nextState.myname.substring(1)
}
}
render() {
return (
<div>
<button onClick={() => {
this.setState({
myname:'heyefan'
})
}}>click</button>
{/* 同名的覆盖掉,不同名的保留 */}
app-{this.state.myname}-{this.state.myage}
</div>
);
}
}
export default App;

修改之前的案例:
javascript
import React, { Component } from 'react'
import axios from 'axios'
export default class App extends Component {
state = {
type:1
}
render() {
return (
<div>
<ul>
<li onClick={()=>{
this.setState({
type:1
})
}}>正在热映</li>
<li onClick={()=>{
this.setState({
type:2
})
}}>即将上映</li>
</ul>
<FilmList type={this.state.type}></FilmList>
</div>
)
}
}
class FilmList extends Component{
state = {
list:[]
}
//初始化-执行一次
componentDidMount() {
// console.log(this.props.type)
if(this.props.type===1){
//请求卖座正在热映的数据
console.log("请求卖座正在热映的数据")
axios({
url:"https://m.maizuo.com/gateway?cityId=110100&pageNum=1&pageSize=10&type=1&k=6369301",
headers:{
'X-Client-Info': '{"a":"3000","ch":"1002","v":"5.2.0","e":"16395416565231270166529","bc":"110100"}',
'X-Host': 'mall.film-ticket.film.list'
}
}).then(res=>{
console.log(res.data.data.films)
this.setState({
list:res.data.data.films
})
})
}else{
//请求卖座即将上映的数据
console.log("请求卖座即将上映的数据")
axios({
url:"https://m.maizuo.com/gateway?cityId=110100&pageNum=1&pageSize=10&type=2&k=8077848",
headers:{
'X-Client-Info': '{"a":"3000","ch":"1002","v":"5.2.0","e":"16395416565231270166529","bc":"110100"}',
'X-Host': 'mall.film-ticket.film.list'
}
}).then(res=>{
console.log(res.data.data.films)
this.setState({
list:res.data.data.films
})
})
}
}
static getDerivedStateFromProps(nextProps,nextState) {
console.log('getDrivedStateFromProps',nextProps)
//根据props和state的变化计算新的state,return的结果会把原来的state对象和返回的对象合并
//此处不能执行异步请求,因为执行完会立马return,这样异步的结果就永远不会返回,也无法进行setState更新
return {
type:nextProps.type
}
}
//通过this.setState把获取的数据转化为状态,配合componentDidUpdate
componentDidUpdate(prevState) {
//getDerivedStateFromProps做多次父子请求最后合并为一次,最后在此处处理
console.log(this.state.type)
if (this.state.type === prevState.type) {
return
}
if(this.state.type===1){
//请求卖座正在热映的数据
console.log("请求卖座正在热映的数据")
axios({
url:"https://m.maizuo.com/gateway?cityId=110100&pageNum=1&pageSize=10&type=1&k=6369301",
headers:{
'X-Client-Info': '{"a":"3000","ch":"1002","v":"5.2.0","e":"16395416565231270166529","bc":"110100"}',
'X-Host': 'mall.film-ticket.film.list'
}
}).then(res=>{
console.log(res.data.data.films)
this.setState({
list:res.data.data.films
})
})
}else{
//请求卖座即将上映的数据
console.log("请求卖座即将上映的数据")
axios({
url:"https://m.maizuo.com/gateway?cityId=110100&pageNum=1&pageSize=10&type=2&k=8077848",
headers:{
'X-Client-Info': '{"a":"3000","ch":"1002","v":"5.2.0","e":"16395416565231270166529","bc":"110100"}',
'X-Host': 'mall.film-ticket.film.list'
}
}).then(res=>{
console.log(res.data.data.films)
this.setState({
list:res.data.data.films
})
})
}
}
render(){
return <ul>
{
this.state.list.map(item=>
<li key={item.filmId}>{item.name}</li>
)
}
</ul>
}
}
getSnapshotBeforeUpdate
实现定位重点功能:
willupdate记录高度
didupdate访问容器高度
scrollTop=此时容器高度-willupdate记录的高度
但是willupdate有副作用,在render的过程中容易被打断,在didupdate访问前有各种不可控事件可能发生,导致记录的高度不准
所以用getSnapshotBeforeUpdate(执行dom前的一刻拍了个shot)取代willupdate,来记录高度,所以必须有返回值
来邮件之后自动滚到之前对应邮件的高度
javascript
import React, { Component } from 'react';
class App extends Component {
state = {
list: [1, 2, 3, 4, 5, 6, 7, 8, 9]
}
myref=React.createRef()
getSnapshotBeforeUpdate() {
//获取容器高度
console.log(this.myref.current.scrollHeight)
return this.myref.current.scrollHeight
}
componentDidUpdate(prevProps,prevState,value) {
console.log(this.myref.current.scrollHeight)
this.myref.current.scrollTop+=this.myref.current.scrollHeight-value
}
render() {
return (
<div>
<button onClick={() => {
this.setState({
list:[...[11,12,13,14,15,16],...this.state.list]
})
}}>来邮件</button>
<h1>邮箱应用</h1>
<div style={{height:'200px',overflow:'auto'}} ref={this.myref}>
<ul>
{
this.state.list.map(item =>
<li key={item} style={{ height: '100px', background: 'pink' }}>{item}</li>
)
}
</ul>
</div>
</div>
)
}
}
export default App;
可以看到添加了几个邮件可以自动跳转到对应的高度
添加css文件以后可以更丝滑
react****中性能优化的方案
1. shouldComponentUpdate
控制组件自身或者子组件是否需要更新,尤其在子组件非常多的情况下, 需要进行优化。
2. PureComponent
在引入的时候替换:
javascript
import React, { PureComponent } from 'react';
class ComponentName extends PureComponent {
render() {
return (
<div>
Content
</div>
);
}
}
export default ComponentName;
PureComponent会帮你 比较新props 跟 旧的props, 新的state和老的state(值相等,或者
对象含有相同的属性、且属性值相等 ),决定shouldcomponentUpdate 返回true 或者
false, 从而决定要不要呼叫 render function。
注意:
如果你的 state 或 props 『永远都会变』,那 PureComponent 并不会比较快,因为
shallowEqual 也需要花时间。