组件的生命周期
生命全周期图
React中组件也有生命周期,也就是说也有很多钩子函数供我们使用, 组件的生命周期,我们会分为四个阶段,初始化、运行中、销毁、错误处理
1.初始化(挂载)
在初始化阶段(当组件实例被创建并插入DOM中时)会执行下面的生命周期
1. constructor
2. static getDerivedStateFromProps()
3. render()
4. componentDidMount()
各生命周期详解
(1) constructor(props)
React组件的构造函数在挂载之前被调用。
在实现React.Component
构造函数时,需要先在添加其他内容前,调用super(props)
,用来将父组件传来的props
绑定到这个类中,使用this.props
将会得到。
官方建议不要在constructor
引入任何具有副作用和订阅功能的代码,这些应当使用componentDidMount()
。
constructor
中应当做些初始化的动作,如:初始化state
,将事件处理函数绑定到类实例上,但也不要使用setState()
。
- 一般在构造函数里做什么事?
- 初始化状态
- 创建ref
- 绑定事件的this指向
constructor(props){
super(props)
console.log("1---执行构造函数")
this.state={
msg:'(*^_^*)'
}
this.inputRef = React.createRef()
this.handleClick = this.handleClick.bind(this)
}
(2) static getDerivedStateFromProps(nextProps,prevState)
getDerivedStateFromProps 会在调用 render方法之前调用,并且在初始挂载及后续更新时都会被调用。
- 它应返回一个对象来更新 state ,如果返回null 则不更新任何内容。
生命全周期图
参数解释: - props 是父组件传来的,有初始化的值就赋给这个state
- return 返回出去的值 会和 state中的值进行合并
- 例子:
static getDerivedStateFromProps(props, state) {
// props 父组件传来的,有初始化的值,赋给这个 state
// 返回的值 会和 state 中的值进行合并
return {
num: state.num || 1,
}
}
使用场景:
- 无条件的根据 prop 来更新内部 state,也就是只要有传入 prop 值, 就更新 state
- 只有 prop 值和 state 值不同时才更新 state 值。
(3) render()
render方法是必须的。当他调用的时候,将计算this.props
和this.state
,并一定要有 返回return
返回的可以是下面的几种类型
- React元素。通过jsx创建,既可以是dom元素,也可以是用户自定义的组件。
- 字符串或数字。他们将会以文本节点形式渲染到dom中。
- Portals。react 16版本中提出的新的解决方案,可以使组件脱离父组件层级直接挂载在DOM树的任何位置。
- null,什么也不渲染
- 布尔值。也是什么都不渲染。
当返回null
,false
,ReactDOM.findDOMNode(this)
将会返回null,什么都不会渲染。
注意: - render()
方法必须是一个纯函数,他不应该改变
state`,也不能直接和浏览器进行交互,应该将事件放在其他生命周期函数中。- 在render()里面调用setState()方法时,会重新渲染 render()方法,造成死循环。
- 如果
shouldComponentUpdate()
返回false
,render()
不会被调用。
(4) componentDidMount
componentDidMount
组件被装配后立即调用。初始化使得DOM节点应该进行到这里。
通常在这里进行ajax请求
如果要初始化第三方的dom库,也在这里进行初始化
只有这里才能获取到真实的dom
例子: 一个简单的小demo
import React, { Component } from 'react'
// 生命周期
export default class App extends Component {
constructor(props) {
super(props)
this.state = {}
console.log("1---执行构造函数")
}
// 从父组件中提取状态
static getDerivedStateFromProps(props, state) {
// props 父组件传来的,有初始化的值,赋给这个 state
// 返回的值 会和 state 中的值进行合并
console.log("2---执行getDerivedStateFromProps")
return {
num: state.num || 1,
}
}
// 告诉大家我已经挂载到 树上了
componentDidMount() {
console.log("4---执行componentDidMount");
// 可以做哪些事情
// 设置一些定时器
// 发送网络请求
// 订阅事件
}
render() {
console.log("3---执行 render函数");
// 在render中不能 改变状态,会死循环!!!
return (
<div>
<span>{this.state.num}</span>
<button>加1</button>
</div>
)
}
}
2.更新阶段
props或state的改变可能会引起组件的更新,组件重新渲染的过程中会调用以下方法:
- static getDerivedStateFromProps()
- shouldComponentUpdate()
- render()
- getSnapShotBeforeUpdate()
- componentDidUpdate()
各生命周期详解
(1) shouldComponentUpdate(nextProps,nextState)
根据shouldComponentUpdate()
的返回值,判断React组件的输出是否受当前 state 或 props 更改的影响
默认行为是 state每次发生变化 ,组件都会重新渲染。
- 当 props 或 state 发生变化时,
shouldComponentUpdate()
会在渲染执行之前被调用 - 返回值默认为true
- 首次渲染或使用
forceUpdate()
时不会调用该方法 - 此方法 是做 性能优化 -> 内置的
PureComponent 组件
- PureComponent 会对 props 和 state 进行浅层比较,并减少了跳过必要更新的可能性。
结合下面的组件状态更新查看此时的例子:PureComponent 组件
- 父组件更新的时候,子组件更新了,但没必要,要减少更新,性能优化,
- PureComponent 在 shouldComponentUpdate
浅比较 值比较值,地址比较地址,地址里面如果内容变化,它不知道. - 如果组件值没有变化,就不会更新
import React, { Component, PureComponent } from "react";
// 生命周期 组件中有组件怎么执行顺序,同理执行
// 高性能组件 继承 PureComponent
class Demo extends PureComponent {
componentDidUpdate() {
console.log("Demo 组件更新了");
}
render() {
return <h3>Demo子组件</h3>;
}
}
export default class App extends Component {
state = {
num: 1,
};
componentDidUpdate() {
console.log("App 组件更新了");
}
render() {
return (
<div>
<h1>App父组件</h1>
<h1>{this.state.num}</h1>
<Demo></Demo>
<button
onClick={() => {
this.setState({
num: this.state.num + 1,
});
}}
>
父组件更新
</button>
</div>
);
}
}
初始界面是:
-
Demo组件继承PureComponent时,子组件中值没有变化,他就不更新。
-
Demo组件继承 Component时,子组件没有变化,它还是更新。
-
如果你一定要手动编写此函数,可以将 this.props 与 nextProps 以及 this.state 与nextState 进行比较,并返回 false 以告知 React 可以跳过更新。请注意,返回 false 并不会阻止子组件在 state 更改时重新渲染。
-
可以查看下面的 组件更新的第一种方式 ——
属性更新的例子
(2) getSnapShotBeforeUpdate(prevProps,prevState)
- 在最近一次渲染输出(提交到DOM节点)之前调用。
它使得组件能在发生更改之前从DOM中捕获一些信息(例如,滚动位置)
此生命周期的任何返回值将作为参数传递给componentDidUpdate()
此用法并不常见,但它可能出现在UI处理中,如需要以特殊方式处理滚动位置的聊天记录等 - 应返回
snapshot
的值(或null)
(3) componentDidUpdate(prevProps,prevState,snapshot)
- 会在更新后被立即调用。首次渲染不会执行此方法。
- 当组件更新后,可以在此处对DOM进行操作。如果你对更新前后的 props 进行了比较,也可以选择在此处进行网络请求
- 例如,当 props 未发生变化时,则不会执行
- 如果组件实现了 getSnapshotBeforeUpdate() 生命周期(不常用),则它的返回值将作为 componentDidUpdate() 的第三个参数 “snapshot” 参数传递。否则此参数将为 undefined。
注意
如果
shouldComponentUpdate()
返回值为 false ,则不会调用 componentDidUpdate()
组件更新时的三种方式:
- new Props 新属性
- setState() 新状态
- forceUpdate 强制更新
例子1:新属性更新的时候
import React, { Component } from "react";
// 属性更新
// 父子之间传值
class Demo extends Component {
render() {
return <h1>{this.props.msg}子组件</h1>;
}
shouldComponentUpdate(nextProps, nextState) {
// 比较更新前后的值是否一致,一致则不更新
if (this.props.msg === nextProps.msg) {
return false;
} else {
return true;
}
}
componentDidUpdate() {
console.log("Demo子组件更新了");
}
}
export default class App extends Component {
state = {
num: 1,
};
componentDidUpdate() {
console.log("App组件更新了");
}
render() {
return (
<div>
<h1>App组件</h1>
<Demo msg={this.state.num}></Demo>
<button
onClick={() => {
this.setState({
num: this.state.num + 1, // 这是组件Demo更新
// num:1, // 这是组件Demo不更新
});
}}
>
更新子组件
</button>
</div>
);
}
}
例子2:新状态更新的时候
import React, { Component } from 'react'
// 组件更新时 三种方式
// 新属性 new Props
// 新状态 setState state ==> Vue data
// 强制更新 forceUpdate
// 下面是 状态更新 的情况
export default class App extends Component {
state={
msg:"😀"
}
// 这个是静态的
// 返回一个对象来更新,合并state,如果返回 null 则不更新任何内容
static getDerivedStateFromProps(props,state){
// 挂载的时候 更新的时候 都会调用
console.log("1-App组件更新 getDerivedStateFromProps");
return null
}
// 这个没有static 如果返回false 截止->视图上没有更新
shouldComponentUpdate(nextProps,nextState){
console.log("2-App组件更新 shouldComponentUpdate");
// return false //不会更新
return true //会更新,不写这个函数,默认返回true
}
// 在最近一次渲染输出(提交到DOM节点)之前调用。
// 它使得组件能在发生更改之前从DOM中捕获一些信息(例如,)
getSnapshotBeforeUpdate(nextProps,nextState){
// 返回的结果作为 componentDidUpdate 第三个参数传入
console.log("4-App组件更新 getSnapshotBeforeUpdate");
return null
}
componentDidUpdate(nextProps,nextState,snapshot){
console.log("5-App组件更新 componentDidUpdate");
console.log(snapshot);
return null
}
render() {
console.log("3-App组件更新 render");
return (
<div>
<h1>{this.state.msg}</h1>
<button onClick={()=>{
this.setState({
msg:"⭐"
})
}}>改变表情</button>
</div>
)
}
}
-
初始界面
-
点击后界面
-
按上述顺序执行
但是:当shouldComponentUpdate()
返回 false 时
shouldComponentUpdate(nextProps,nextState){
console.log("2-App组件更新 shouldComponentUpdate");
return false //不会更新
// return true //会更新,不写这个函数,默认返回true
}
- 点击后的页面是
新状态更新的时 —— 滚动的例子:
import React, { Component } from "react";
// 例子:点击跳转到指定的滚动位置
// 2-引入大小盒子的样式
import "./App.css"
export default class App extends Component {
// 5-在构造函数中创建 ref
constructor(props){
super(props)
this.boxRef = React.createRef()
}
state = {
msg: "😀",
};
static getDerivedStateFromProps(props, state) {
console.log("1-App组件更新 getDerivedStateFromProps");
return null;
}
shouldComponentUpdate(nextProps, nextState) {
console.log("2-App组件更新 shouldComponentUpdate");
return true;
}
// 返回的结果 会作为 componentDidUpdate 第三个参数传入 (例如,滚动位置)
getSnapshotBeforeUpdate(nextProps, nextState) {
console.log("4-App组件更新 getSnapshotBeforeUpdate");
return 500;
}
componentDidUpdate(nextProps, nextState, snapshot) {
console.log("5-App组件更新 componentDidUpdate");
// 6-在这里,组件更新后 获取外部盒子节点
console.log(this.boxRef.current);
// 7-给外部盒子添加 滚动
this.boxRef.current.scrollTop = snapshot
return null;
}
render() {
console.log("3-App组件更新 render");
return (
// 1-滚动条件:内部盒子高于外部盒子
// 4-是控制外部盒子的滚动条的位置,所以要获取外部盒子 这个节点 使用 ref绑定
<div className="outerBox" ref={this.boxRef}>
<div className="innerBox">
<h1>{this.state.msg}</h1>
{/* 3-设置事件当点击的时候,滚动条跳转到指定位置 */}
<button
onClick={() => {
this.setState({
msg: "⭐",
});
}}
>
改变表情
</button>
</div>
</div>
);
}
}
- 开始界面
例子3:强制更新的时候
就是一个forceUpdate()方法。
import React, { Component } from 'react'
// 组件更新的第三种方式
// 强制更新 forceUpdate
export default class App extends Component {
// 2-定义事件 调用forceUpdate()方法
handleForceUpdate=(params) => {
this.forceUpdate(()=>{
console.log("调用forceUpdate()强制更新");//2
})
}
// 3-这里观看有没有更新
componentDidUpdate(){
console.log("组件更新了"); //1
}
render() {
return (
<div>
{/* 1-点击事件触发函数 */}
<button onClick={this.handleForceUpdate}>强制更新</button>
</div>
)
}
}
3.卸载阶段
1. componentWillUnmount()
生命周期详解
(1) componentWillUnmount()
会在组件卸载及销毁之前直接调用。
在此方法中执行必要的清理操作
例如:
- 清除timer
- 取消网络请求
- 清除在
componentDidMount()
中创建的订阅
a 卸载时简单例子
放在卸载了的组件中,才执行
卸载之后再html中就不显示了
import React, { Component } from "react";
// 定义了一个子组件 Demo
class Demo extends Component {
// 哪个组件销毁了,哪个里面就执行这个
componentWillUnmount(){
console.log("组件卸载了");
}
render() {
return <h1>Demo组件</h1>;
}
}
export default class App extends Component {
state = {
show: true,
}
// 点击事件 改变show的值
handleClick=(params) => {
this.setState({
show:false
})
}
render() {
console.log("render执行了");
return (
<div>
<h1>App组件</h1>
{/* <Demo></Demo> */}
{/* 动态卸载Demo */}
{/* 依据show的值来判断 显示 何 */}
{this.state.show ? <Demo></Demo> : "Demo卸载了"}
<button onClick={this.handleClick}>点击卸载Demo组件</button>
</div>
);
}
}
b 组件卸载阶段的一些操作
- 清楚定时器
- 取消网络请求(安装了axios)
- 取消订阅
import React, { Component } from 'react'
// componentWillUnmount()会在组件卸载及销毁之前直接调用,在此方法中执行必要的清理操作
// 组件卸载阶段做的一些事
// 1.清除定时器
// 2.取消网络请求, 网络请求要来引入axios
import axios from 'axios'
// 3.清除在componentDidMount()中创建的订阅
class Demo extends Component {
constructor(props) {
super(props)
// b-1-1创建CancelToken
let CancelToken = axios.CancelToken
// b-1-2调用CancelToken的dource方法
this.source = CancelToken.source()
}
// 在组建挂载的时候做的是组件卸载相反的事
componentDidMount() {
/* a-1设置定时器 */
this.timerId = setInterval(function () {
console.log("定时器运行了");
}, 1000)
/* b-1 axios设置网络请求 */
axios.get("https://apimusic.linweiqin.com/search?keywords=%27%E7%83%AD%E9%97%A8%27", {
// b-1-3看状态里
cancelToken: this.source.token
}).then(res => {
console.log(res);
})
/* c-1 设置订阅事件 */
window.addEventListener('click', this.handleClick)
}
componentWillUnmount() {
/* a-2 清除定时器 */ //此时要给定时器设置一个名称
clearInterval(this.timerId)
console.log("Demo组件卸载了");
/* b-2 怎么取消网络请求???? */ //使用source.cancel
this.source.cancel('取消本次请求了')
/* c-2 取消订阅 */ //移除事件
window.removeEventListener('click', this.handleClick)
}
handleClick = () => {
console.log("window被点击了");
}
render() {
return <h1>Demo组件🐖</h1>
}
}
export default class App extends Component {
state = {
show: true
}
render() {
return (
<div>
<h1>App组件</h1>
{this.state.show ? <Demo></Demo> : ""}
<button onClick={() => {
this.setState({
show: false
})
}}>点击卸载</button>
</div>
)
}
}
问:CancelToken是干嘛用的??
先占张图,明天再看
4.错误处理
- componentDidCatch()