函数式组件只有属性,没有状态,更没有生命周期的概念,但是函数式组件在React16.8引入hooks以后,可以在函数式组件里使用state和生命周期等特性,拥有类似于类组件的能力,同时保持简洁和易用性。
在生命周期的某个阶段会触发的东西
写不写本身不会影响组件的执行,但是写的话会在生命周期的某个阶段触发
整个生命中周期分为三个阶段:初始化阶段、运行阶段、销毁阶段
初始化阶段

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版本的重要节点:16.2之前都是老版本的生命周期函数,使用的时候可以自己将其重命名为UNSAFE_componentWillMount,相当于自己提醒自己
警告消失了:


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;
运行中阶段

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;

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,),来判断组件是否应该更新:
shouldComponentUpdate(nextProps,nextState) {//return true//应该更新//return false//阻止更新//做条件判断:if (this.state.myname!==nextState.myname) {return true}return false
}
如果要对比的内容是对象,可以转化为json字符串对比:
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 <divstyle={{ 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;
最先获取父组件传来的属性,相当于子组件里的初始化
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)//第一次点击按钮拿到的是老属性,11111111console.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
/** @作者: 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>}
}
销毁阶段
例如你在组件里写了一个定时器,即使组件删除了定时器也还在,就会降低性能,这时候就需要在销毁阶段销毁定时器
进行清理操作的生命周期函数
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转为首字母大写
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;
修改之前的案例:
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把获取的数据转化为状态,配合componentDidUpdatecomponentDidUpdate(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,来记录高度,所以必须有返回值
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;
可以看到添加了几个邮件可以自动跳转到对应的高度


react中性能优化的方案
1. shouldComponentUpdate
控制组件自身或者子组件是否需要更新,尤其在子组件非常多的情况下, 需要进行优化。
2. PureComponent
在引入的时候替换:
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 也需要花时间。