React生命周期
生命周期-初始化
import React, { Component } from 'react'export default class App extends Component {componentWillMount(){console.log("第一次will mount")}componentDidMount(){console.log("第一次did mount")}render() {console.log("render")return ( <div></div>)}
}
componentWillMount是第一次上树前的最后一次修改状态
import React, { Component } from 'react'export default class App extends Component {state = {myname: 'switch',}componentWillMount() {console.log('第一次will mount',this.state.myname,document.getElementById('myname'))//第一次上树前 的最后一次修改状态this.setState({myname: 'Switch',})}componentDidMount() {console.log('第一次did mount', document.getElementById('myname'))}render() {console.log('render')return (<div><span id="myname">{this.state.myname}</span></div>)}
}
可以起到一个初始化数据的作用
import React, { Component } from 'react'export default class App extends Component {state = {myname: 'switch',}componentWillMount() {console.log('第一次will mount',this.state.myname,document.getElementById('myname'))//第一次上树前 的最后一次修改状态this.setState({myname: 'Switch',})}componentDidMount() {console.log('第一次did mount', document.getElementById('myname'))//数据请求axios//订阅函数的调用//setInterval// 基于创建完的dom进行初始化(BetterScroll)}render() {console.log('render')return (<div><span id="myname">{this.state.myname}</span></div>)}
}
初始化注意
import React, { Component } from 'react'export default class App extends Component {state = {myname: 'switch',}componentWillMount() {console.log('第一次will mount',this.state.myname,document.getElementById('myname'))//第一次上树前 的最后一次修改状态this.setState({myname: 'Switch',})}componentDidMount() {console.log('第一次did mount', document.getElementById('myname'))//数据请求axios//订阅函数的调用//setInterval// 基于创建完的dom进行初始化(BetterScroll)}render() {console.log('render')return (<div><span id="myname">{this.state.myname}</span></div>)}
}
之前那样写会报黄色的警告,因为优先级比较低,在执行过程中可能会被优先级较高的任务打断,改写之后就相当于告诉它,我是不安全的,就不会报警告了
Fiber 是 React 16 中引入的一个重大更新,它是一种新的协调算法,将渲染工作分割成多个小的任务单元,执行每个任务单元时可以被中断和恢复。在 React 的生命周期中,Fiber 的工作可以分为两个阶段:
- Reconciliation 阶段:这个阶段 React 会找出需要更新的组件,并生成一个新的 Fiber 树。在这个阶段,React 会根据组件的 props 和 state 的变化,计算出哪些组件需要更新,并生成一个新的 Fiber 树。由于这个阶段是可以被中断和恢复的,所以 React 可以在这个阶段进行一些优化,例如暂停、终止或者重新排序任务。
- Commit 阶段:这个阶段 React 会将新的 Fiber 树应用到 DOM 上,完成页面的更新。在这个阶段,React 会将新的 Fiber 树应用到 DOM 上,完成页面的更新。由于这个阶段是不可以被中断的,所以 React 会一次性完成所有的 DOM 操作。
Fiber 的引入,使得 React 的渲染过程更加高效和灵活。它可以让 React 在渲染过程中更好地利用浏览器的空闲时间,避免了长时间占用主线程导致的卡顿问题。同时,Fiber 还可以让 React 更好地处理异步操作和错误处理,提高了应用的性能和稳定性。
初始化案例
之前写的可以改写,把axios请求放到componentDidMount()
import React, { Component } from 'react'
import axios from 'axios'
import { extend } from 'lodash'export default class App extends Component {constructor() {super()this.state = {filmList: [],info: '',}}componentDidMount() {axios.get(`/cartData.json`).then((res) => {console.log(res.data.list)this.setState({filmList: res.data.list,})})}render() {return (<div>{this.state.info}{this.state.filmList.map((item) => (<FilmItemkey={item.id}{...item}onEvent={(value) => {// console.log("父组件接收",value)this.setState({info: value,})}}></FilmItem>))}<FilmDetail info={this.state.info}></FilmDetail></div>)}
}class FilmItem extends Component {render() {// console.log(this.props)let { name, poster, synopsis } = this.propsreturn (<div className="filmitem"><img src={poster} alt={name} /><h4>{name}</h4>{this.props.name}</div>)}
}class FilmDetail extends Component {render() {return <div className="filmdetail">{this.props.info}</div>}
}
这样改写没毛病
为什么一定要基于创建完的dom进行初始化呢?
有的库是纯js写的需要进行dom操作,不知道dom什么时候创建完它就没有办法工作
import React, { Component } from 'react'
import BetterScroll from 'better-scroll'export default class App extends Component {state = {list:["111","222","333","444","555","666","777","888","999","049","120","911","110"]}componentDidMount(){new BetterScroll("#wrapper")}render() {return (<div><div id='wrapper' style={{height:"200px",background:"yellow",overflow:"hidden"}}><ul>{this.state.list.map(item=>(<li key={item}>{item}</li>))}</ul></div></div>)}
}
在执行完之后就可以顺利的滚动了
生命周期-运行中
来看一段代码:
import React, { Component } from 'react'export default class App extends Component {state = {myname:"switch"}render() {console.log("render")return (<div><button onClick={()=>{this.setState({myname:"steam"})}}>click</button>{this.state.myname}</div>)}componentWillUpdate(){console.log("componentWillUpdate")}componentDidUpdate(){ console.log("componentDidUpdate")}
}
搞一个滚动条:
import React, { Component } from 'react'
import axios from 'axios'
import BetterScroll from 'better-scroll'export default class App extends Component {state = {myname:"switch",list:[]}componentDidMount(){axios.get("/test.json").then(res=>{console.log(res.data.data.films)this.setState({list:res.data.data.films},()=>{// 访问真实domnew BetterScroll("#warpper")})})}render() {console.log("render")return (<div><button onClick={()=>{this.setState({myname:"steam"})}}>click</button><span id='myname'>{this.state.myname}</span><div id='warpper' style={{height:"100px",overflow:"hidden",background:"yellow"}}><ul>{this.state.list.map(item=><li key={item.filmId}>{item.name}</li>)}</ul></div></div>)}componentWillUpdate(){console.log("componentWillUpdate",document.getElementById("myname").innerHTML)}componentDidUpdate(){ console.log("componentDidUpdate",document.getElementById("myname").innerHTML)}
}
在componentDidMount
中,当你调用new BetterScroll("#wrapper")
时,此时this.state.list
可能还没有被更新,也就是说#wrapper
内的内容可能还没有完全渲染出来。BetterScroll
需要在内容已经渲染好并且有足够高度差(内容高度大于容器高度)的情况下才能正常工作。
你可以尝试在setState
的回调函数中初始化BetterScroll
,这样可以确保在内容更新完成后再进行初始化
除了这一种方式还有别的方法吗?
这样也可以:
import React, { Component } from 'react'
import axios from 'axios'
import BetterScroll from 'better-scroll'export default class App extends Component {state = {myname:"switch",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:"steam"})}}>click</button><span id='myname'>{this.state.myname}</span><div id='warpper' style={{height:"100px",overflow:"hidden",background:"yellow"}}><ul>{this.state.list.map(item=><li key={item.filmId}>{item.name}</li>)}</ul></div></div>)}componentWillUpdate(){console.log("componentWillUpdate",document.getElementById("myname").innerHTML)}componentDidUpdate(){ console.log("componentDidUpdate",document.getElementById("myname").innerHTML)new BetterScroll("#warpper")}
}
在DidupState中可以接受到老的属性和老的状态
这样改可以避免被初始化多次:
import React, { Component } from 'react'
import axios from 'axios'
import BetterScroll from 'better-scroll'export default class App extends Component {state = {myname:"switch",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:"steam"})}}>click</button><span id='myname'>{this.state.myname}</span><div id='warpper' style={{height:"100px",overflow:"hidden",background:"yellow"}}><ul>{this.state.list.map(item=><li key={item.filmId}>{item.name}</li>)}</ul></div></div>)}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("#warpper")}new BetterScroll("#warpper")}
}
每点一次都会更新,走render函数
import React, { Component } from 'react'export default class App extends Component {state = {myname:"kerwin"}render() {return (<div><button onClick={()=>{this.setState({myname:"xiaoming"})}}>{this.state.myname}</button></div>)}
}
更不更新由一个函数来决定:
import React, { Component } from 'react'export default class App extends Component {state = {myname:"kerwin"}render() {return (<div><button onClick={()=>{this.setState({myname:"xiaoming"})}}>{this.state.myname}</button></div>)}shouldComponentUpdate(){return true //应该更新// return false //不应该更新}
}
想要提高性能应该在这里设置条件
import React, { Component } from 'react'export default class App extends Component {state = {myname: 'kerwin',}render() {console.log("render")return (<div><buttononClick={() => {this.setState({myname: 'xiaoming',})}}>{this.state.myname}</button></div>)}shouldComponentUpdate(nextProps, nextState) {if (this.state.myname !== nextState.myname) {return true //应该更新}return false //不应该更新}
}
这样一个一个状态的比还是太过于难以描述了,我们可以将对象转成字符串的比:
import React, { Component } from 'react'export default class App extends Component {state = {myname: 'kerwin',}render() {console.log("render")return (<div><buttononClick={() => {this.setState({myname: 'xiaoming',})}}>{this.state.myname}</button></div>)}shouldComponentUpdate(nextProps, nextState) {if (JSON.stringify(this.state) !== JSON.stringify(nextState)) {return true //应该更新}return false //不应该更新}
}
科文老师说有的把shouldComponentUpdate教程scu被人听成icu导致说了一大堆为公司鞠躬尽瘁死而后已的话,让我想起了肘学长简历上面写的什么喜欢打球可以接受高强度加班。
import { curry } from 'lodash'
import React, { Component } from 'react'class Box extends Component{render(){return <div style={{width:"100px",height:"100px",border:this.props.current===this.props.index?'5px solid red':'1px solid gray',margin:"10px",float:'left'}}></div>}
}export default class App extends Component { state = {list:["00","01","02","03","04","05","06","07","08","09"],current:5} render() {return (<div><input type='number' onChange={(evt)=>{this.setState({current:Number(evt.target.value)})}}/><div style={{overflow:"hidden"}}>{this.state.list.map((item,index)=>{return <Box key={item} current={this.state.current} index={index}/>})}</div></div>)}}
真是太好玩了。可以跟着我的点击走
但是这份代码目前还存在着一定的问题
每一个小盒子都会被重新的渲染,我该如何优化?
这样进行更改,按条件来决定是否重新进行渲染:
import { curry } from 'lodash'
import React, { Component } from 'react'class Box extends Component{shouldComponentUpdate(nextProps){if(this.props.current === this.props.index || nextProps.current === nextProps.index){return true}return false}render(){console.log("render")return <div style={{width:"100px",height:"100px",border:this.props.current===this.props.index?'5px solid red':'1px solid gray',margin:"10px",float:'left'}}></div>}
}export default class App extends Component { state = {list:["00","01","02","03","04","05","06","07","08","09"],current:5} render() {return (<div><input type='number' onChange={(evt)=>{console.log(evt.target.value)this.setState({current:Number(evt.target.value)})}} value={this.state.current}/><div style={{overflow:"hidden"}}>{this.state.list.map((item,index)=>{return <Box key={item} current={this.state.current} index={index}/>})}</div></div>)}}
不能直接对状态进行修改的原因就是,会通过this.state来看是否渲染,如果提前修改可能连第一次都不执行了
componentWillReceiveProps:父组件修改属性触发
import React, { Component } from 'react'class Child extends Component{render(){return <div>child</div>}componentWillReceiveProps(){console.log("componentWillReceiveProps")}
}export default class App extends Component {render() {return (<div><Child/></div>)}
}
子组件不论怎么作妖,这个componentWillReceiveProps都是不会理的
在父组件中做一次状态的更新也会引发孩子的生命周期的更新
import React, { Component } from 'react'class Child extends Component{render(){return <div>child</div>}componentWillReceiveProps(){console.log("componentWillReceiveProps")}
}export default class App extends Component {state={text:"111111111"}render() {return (<div>{this.state.text}<button onClick={()=>{this.setState({text:"2222222222"})}}>click</button><Child/></div>)}
}
这个componentWillReceiveProps存在的意义就是:可以最先获得父组件传来的属性,可以利用属性进行ajax或者逻辑处理,把属性转化成孩子自己的状态
import React, { Component } from 'react'class Child extends Component{state = {title:""}render(){return <div>child-{this.state.title}</div>}componentWillReceiveProps(nextProps){console.log("componentWillReceiveProps",nextProps)//最先获得父组件传来的属性,可以利用属性进行ajax或者逻辑处理//把属性转化成孩子自己的状态this.setState({title:nextProps.text + "kerwin"})}
}export default class App extends Component {state={text:"111111111"}render() {return (<div>{this.state.text}<button onClick={()=>{this.setState({text:"2222222222"})}}>click</button><Child text={this.state.text}/></div>)}
}
写一个实例:
import React, { Component } from 'react'
import axios from 'axios'export default class App extends Component {constructor(props){super(props)this.state = {type:1}}render() {return (<div><ul><lionClick={() => {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:[]}//初始化-执行一次,只会更新,不会再度创建conponentDidMount(){// console.log(this.props.type)if(this.props.type === 1){//请求卖座正在热映的数据console.log("请求卖座正在热映的数据")axios({url:"https://m.maizuo.com/gateway?cityId=610100&pageNum=1&pageSize=10&type=1&k=4202475",headers:{'x-client-info':'{"a":"3000","ch":"1002","v":"5.2.1","e":"1741595630655347584860161","bc":"610100"}','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=610100&pageNum=1&pageSize=10&type=2&k=7812849",headers:{'x-client-info':'{"a":"3000","ch":"1002","v":"5.2.1","e":"1741595630655347584860161"}','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=610100&pageNum=1&pageSize=10&type=1&k=4202475",headers:{'x-client-info':'{"a":"3000","ch":"1002","v":"5.2.1","e":"1741595630655347584860161","bc":"610100"}','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=610100&pageNum=1&pageSize=10&type=2&k=7812849",headers:{'x-client-info':'{"a":"3000","ch":"1002","v":"5.2.1","e":"1741595630655347584860161"}','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>}
}
老声命周期存在一些问题:
(1) componentWillMount ,在ssr中 这个方法将会被多次调用, 所以会重复触发多遍,同时在这里如果绑定事件, 将无法解绑,导致内存泄漏 , 变得不够安全高效逐步废弃。
(2) componentWillReceiveProps 外部组件多次频繁更新传入多次不同的 props,会导致不必要的异步请求
(3) componetWillupdate, 更新前记录 DOM 状态, 可能会做一些处理,与componentDidUpdate相隔时间如果过 长, 会导致 状态不太信
生命周期-销毁
如果销毁的话,绑在window窗口上的事件是不会消失的
import React, { Component } from 'react'export default class App extends Component {state = {isCreated:true}render() {return (<div><button onClick={()=>{this.setState({isCreated:!this.state.isCreated})}}>click</button>{this.state.isCreated&&<Child/>}</div>)}
}class Child extends Component{render(){return <div>child</div>}componentDidMount(){window.onresize=()=>{//窗口大小的监听console.log("resize")}}componentWillUnmount(){console.log("componentWillUnmount")}
}
可以这样清除绑在window上的事件:
import React, { Component } from 'react'export default class App extends Component {state = {isCreated:true}render() {return (<div><button onClick={()=>{this.setState({isCreated:!this.state.isCreated})}}>click</button>{this.state.isCreated&&<Child/>}</div>)}
}class Child extends Component{render(){return <div>child</div>}componentDidMount(){window.onresize=()=>{//窗口大小的监听console.log("resize")}this.timer=setInterval(()=>{console.log("1111")},1000)}componentWillUnmount(){console.log("componentWillUnmount")window.onresize = nullclearInterval(this.timer) //清除定时器}
}
新生命周期-getDerivedStateFromProps
getDerivedStateFromProps 第一次的初始化组件以及后续的更新过程中(包括自身状态更新以及父传子) , 返回一个对象作为新的state,返回null则说明不需要在这里更新state
这个相当于代替了componentWillMount和componentWillupdate
import React, { Component } from 'react'export default class App extends Component {state = {myname:"kerwin",myage:100}static getDerivedStateFromProps(){//这是类方法不是对象方法console.log("getDrivedStateFromProps")return {//可以在这做最后一次的修改myname:"Kerwin"}}render() {return (<div><button onClick={()=>{this.setState({myname:"xiaoming"})}}>click</button>App -{this.state.myname} - {this.state.myage} </div>)}
}
这里更改了状态之后就是一直更改,然后一直被修改成大写字母开头的
初始化和更新都要做的逻辑可以合并在一起:
import React, { Component } from 'react'export default class App extends Component {state = {myname:"kerwin",myage:100}static getDerivedStateFromProps(nextProps,nextState){//这是类方法不是对象方法console.log("getDrivedStateFromProps",nextState)return {//可以在这做最后一次的修改myname:nextState.myname.substring(0,1).toUpperCase()+nextState.myname.substring(1)}}render() {return (<div><button onClick={()=>{this.setState({myname:"xiaoming"})}}>click</button>App -{this.state.myname} - {this.state.myage} </div>)}
}
静态方法里面不能调用this
案例
新生命周期和老生命周期不能共存
import React, { Component } from 'react'
import axios from 'axios'export default class App extends Component {constructor(props){super(props)this.state = {type:1}}render() {return (<div><ul><lionClick={() => {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:[],type:1}//初始化-执行一次,只会更新,不会再度创建conponentDidMount(){// console.log(this.props.type)if(this.props.type === 1){//请求卖座正在热映的数据console.log("请求卖座正在热映的数据")axios({url:"https://m.maizuo.com/gateway?cityId=610100&pageNum=1&pageSize=10&type=1&k=4202475",headers:{'x-client-info':'{"a":"3000","ch":"1002","v":"5.2.1","e":"1741595630655347584860161","bc":"610100"}','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=610100&pageNum=1&pageSize=10&type=2&k=7812849",headers:{'x-client-info':'{"a":"3000","ch":"1002","v":"5.2.1","e":"1741595630655347584860161"}','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){
// }static getDerivedStateFromProps(nextProps,nextState){//这是类方法不是对象方法console.log("getDrivedStateFromProps",nextState)return {type:nextProps.type}
}componentDidUpdate(prevProps,prevState){console.log(this.state.type)if(this.state.type=== prevProps.type){return}if(this.state.type === 1){//请求卖座正在热映的数据console.log("请求卖座正在热映的数据")axios({url:"https://m.maizuo.com/gateway?cityId=610100&pageNum=1&pageSize=10&type=1&k=4202475",headers:{'x-client-info':'{"a":"3000","ch":"1002","v":"5.2.1","e":"1741595630655347584860161","bc":"610100"}','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=610100&pageNum=1&pageSize=10&type=2&k=7812849",headers:{'x-client-info':'{"a":"3000","ch":"1002","v":"5.2.1","e":"1741595630655347584860161"}','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
getSnapshotBeforeUpdate 取代了 componetWillUpdate ,触发时间为update发生的时候,在render之后 dom渲染之前返回一个值,作为componentDidUpdate的第三个参数
假设你要实现这样的一个功能:
import React, { Component } from 'react'export default class App extends Component {state = {mytext:"11111"}render() {console.log("render")return (<div>app<button onClick={()=>{this.setState({mytext:"22222"})}}>click</button>{this.state.mytext}</div>)}
// componentWillUpdate(){
// console.log("componentWillUpdate")
// }
//需要注释掉,因为不能同时的使用componentDidUpdate(prevProps,prevState,value){console.log("componentDidUpdate",value)}getSnapshotBeforeUpdate(){console.log("getSnapshotBeforeUpdate")return 100}
}
这样就可以实现不变化的实现这个效果:
import React, { Component } from 'react'export default class App extends Component {state={list:[1,2,3,4,5,6,7,8,9]}myref = React.createRef() getSnapshotBeforeUpdate(){//获取容器高度return this.myref.current.scrollHeight}componentDidUpdate(prevProps,prevState,value){this.myref.current.scrollTop += this.myref.current.scrollHeight-value}render() {return (<div><button onClick={()=>{this.setState({list:[...[11,12,13,14,15,16,17,18,19],...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:"yellow"}}>{item}</li>)}</ul></div></div>)}
}