往期回顾:
- # React基础入门之虚拟Dom【一】
- # React面向组件编程【二】
- # 彻底搞懂React类组件中的实例属性state【三】
- # React组件中props的使用【四】
- # 详细解读React类组件中的refs属性【五】
- # React中的事件处理【六】
- # React中的受控组件与非受控组件【七】
同vue一样,react也是有生命周期的。我们通过下面这个demo来学习react 的生命周期如何在类组件中实现。
react声明周期钩子简介
生命周期函数是React
组件中的一些方法,它们在组件的不同状态下被调用,可以在这些函数中实现一些特定的行为。React组件的生命周期可以分为三个阶段:
挂载阶段(Mounting):组件被创建并插入到DOM中
更新阶段(Updating):组件被重新渲染
卸载阶段(Unmounting):组件被从DOM中移除
下面是React中常用的生命周期函数:
- constructor(props):初始化组件的状态和属性。
- getDerivedStateFromProps(props, state):在组件更新前被调用。返回值将会被添加到组件的状态中。
- render():渲染组件。必须返回一个React元素或null。
- componentDidMount():组件已经被挂载到DOM中。可以在这个函数中发送请求。
- shouldComponentUpdate(nextProps, nextState):组件将要更新。可以在这个函数中判断是否需要更新组件。
- getSnapshotBeforeUpdate(prevProps, prevState):在组件更新前被调用。返回值会传递给componentDidUpdate()函数。
- componentDidUpdate(prevProps, prevState, snapshot):组件已经更新完毕。可以在这个函数中更新状态或者发送请求。
- componentWillUnmount():组件将要被卸载,可以在这个函数中清除定时器或者取消请求。
react的生命周期写钩子使用示例
如图,页面加载时,页面文字透明度逐渐降低,当透明度小于零时,透明度变为1;当点击按钮时,页面卸载。
分析这个页面,我们知道当页面加载时,需要触发文字渐变相关函数;点击按钮实现页面注销。
unmountComponentAtNode
我们先实现页面销毁
js
// 1、创建类组件
class Life extends React.Component {
tangPing = () => {
ReactDOM.unmountComponentAtNode(document.getElementById("test"));
};
render() {
return (
<div>
<h1 style={{ opacity: this.state.opacity }}>好好学习,天天向上</h1>
<button onClick={this.tangPing}>学个锤子</button>
</div>
);
}
}
// 渲染组件
ReactDOM.render(<Life />, document.getElementById("test"));
上述代码中,我们通过ReactDOM.unmountComponentAtNode方法实现了类组件的销毁。unmountComponentAtNode顾名思义,销毁组件在某个节点,因此,其入参就是当前节点id
componentDidMount
componentDidMount是组件挂载到DOM中的钩子,类似于vue中的mount钩子。我们在这个钩子中添加一些逻辑。
js
class Life extends React.Component {
state = {
opacity: 1,
};
tangPing = () => {
ReactDOM.unmountComponentAtNode(document.getElementById("test"));
};
// 组件已经被挂载到DOM中
componentDidMount() {
setInterval(() => {
// 获取原状态
let { opacity } = this.state;
// 减少0.1
opacity -= 0.1;
// 设置新的透明度
if (opacity <= 0) opacity = 1;
this.setState({ opacity });
}, 200);
}
render() {
return (
<div>
<h1 style={{ opacity: this.state.opacity }}>好好学习,天天向上</h1>
<button onClick={this.tangPing}>学个锤子</button>
</div>
);
}
}
componentDidMount同render函数一样,是类原型上的方法,所以采用componentDidMount() {}的写法而不采用componentDidMount = () =>{}的写法。
页面效果:
可以看出,页面效果实现了,但是,当我们销毁组件时,控制台有报错。
报错原因可以看出来,组件销毁了,setInterval执行时,找不到state状态值了。要解决这个问题,最简单的,就是销毁组件时,清空定时器:
js
tangPing = () => {
clearInterval(this.timer);
ReactDOM.unmountComponentAtNode(document.getElementById("test"));
};
// 组件已经被挂载到DOM中
componentDidMount() {
this.timer = setInterval(() => {
// 获取原状态
let { opacity } = this.state;
// 减少0.1
opacity -= 0.1;
// 设置新的透明度
if (opacity <= 0) opacity = 1;
this.setState({ opacity });
}, 200);
}
componentWillUnmount
除了用上面的方法清空定时器,我们也可以在页面注销钩子里清空定时器。
componentWillUnmount是组件将要被卸载时的钩子。类似于vue的beforeDestory钩子,可以在这个函数中清除定时器或者取消请求。
js
class Life extends React.Component {
state = {
opacity: 1,
};
tangPing = () => {
ReactDOM.unmountComponentAtNode(document.getElementById("test"));
};
componentWillUnmount() {
clearInterval(this.timer);
}
// 组件已经被挂载到DOM中
componentDidMount() {
this.timer = setInterval(() => {
// 获取原状态
let { opacity } = this.state;
// 减少0.1
opacity -= 0.1;
// 设置新的透明度
if (opacity <= 0) opacity = 1;
this.setState({ opacity });
}, 200);
}
render() {
return (
<div>
<h1 style={{ opacity: this.state.opacity }}>好好学习,天天向上</h1>
<button onClick={this.tangPing}>学个锤子</button>
</div>
);
}
}
React类组件中旧版生命周期钩子详解
本章我们将讨论react旧版的生命周期钩子函数,以便更好的学习react最新版钩子函数。
react组件挂载时的钩子函数
react组件挂载时的钩子执行顺序如图
我们先看一个简单的求和案例
xml
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>hello_react</title>
</head>
<body>
<!-- 准备好一个"容器" -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="./js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="./js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="./js/babel.min.js"></script>
<script type="text/babel">
// 1、创建类组件
class Count extends React.Component {
state = {
count: 0,
};
add = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<div>
<h1>{this.state.count}</h1>
<button onClick={this.add}>点我+1</button>
</div>
);
}
}
// 渲染组件
ReactDOM.render(<Count />, document.getElementById("test"));
</script>
</body>
</html>
页面效果
现在,我们将钩子函数写入上述代码中,进行验证
javascript
class Count extends React.Component {
constructor(props) {
console.log("Count的constructor函数钩子");
super(props);
this.state = {
count: 0,
};
}
// 组价挂载前
componentWillMount() {
console.log("Count的componentWillMount函数钩子");
}
add = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
console.log("Count的render函数钩子");
return (
<div>
<h1>{this.state.count}</h1>
<button onClick={this.add}>点我+1</button>
</div>
);
}
// 组价挂载完成
componentDidMount() {
console.log("Count的componentDidMount函数钩子");
}
}
上述代码中,我们使用了constructor,我了代码一致性,我们将state写在了constructor内部。
我们看看钩子的执行顺序
通过上图,我们可以看出组件挂载过程中,钩子函数的执行顺序是:constructor、componentWillMount、render及componentDidMount
注意,render钩子在每次页面有更新时,都会执行一次。
react组件更新时的钩子
当组件有更新时,组件也会触发一系列钩子函数
setState系列钩子
我们先看最简单的setState这条线的钩子
js
class Count extends React.Component {
constructor(props) {
console.log("Count的constructor函数钩子");
super(props);
this.state = {
count: 0,
};
}
// 组价挂载前
componentWillMount() {
console.log("Count的componentWillMount函数钩子");
}
add = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
console.log("Count的render函数钩子");
return (
<div>
<h1>{this.state.count}</h1>
<button onClick={this.add}>点我+1</button>
</div>
);
}
// 组价挂载完成
componentDidMount() {
console.log("Count的componentDidMount函数钩子");
}
// 控制是否允许组件更新的钩子,需要返回一个布尔值
shouldComponentUpdate() {
console.log("组件状态更改控制钩子:shouldComponentUpdate");
return true;
}
// 组件更新前
componentWillUpdate() {
console.log("组件更新前:ComponentWillUpdate");
}
// 组件更新完成
componentDidUpdate() {
console.log("组件更新完成:componentDidUpdate");
}
}
通过下图,当组件挂载时,以此执行constructor、componentWillMount、render、componentDidMount钩子;组件state更新时,依次触发shouldComponentUpdate、componentWillUpdate、render、componentDidUpdate钩子。
注意,shouldComponentUpdate是一个特殊钩子,是控制是否允许更新state状态值的,他需要返回一个布尔值,代表是否更新state的值
forceUpdate系列钩子
当页面有状态更改时,会触发setState系列钩子。但有时候,我们不想更改任何状态值也想上页面更新,这个时候就会触发forceUpdate系列钩子。forceUpdate系列钩子只比setState钩子少一个shouldComponentUpdate生命周期。
我们看一个demo
js
class Count extends React.Component {
constructor(props) {
console.log("Count的constructor函数钩子");
super(props);
this.state = {
count: 0,
};
}
// 组价挂载前
componentWillMount() {
console.log("Count的componentWillMount函数钩子");
}
add = () => {
this.setState({ count: this.state.count + 1 });
};
force = () => {
this.forceUpdate();
};
render() {
console.log("Count的render函数钩子");
return (
<div>
<h1>{this.state.count}</h1>
<button onClick={this.add}>点我+1</button>
<button onClick={this.force}>强制更新</button>
</div>
);
}
// 组价挂载完成
componentDidMount() {
console.log("Count的componentDidMount函数钩子");
}
// 控制是否允许组件更新的钩子,需要返回一个布尔值
shouldComponentUpdate() {
console.log("组件状态更改控制钩子:shouldComponentUpdate");
return true;
}
// 组件更新前
componentWillUpdate() {
console.log("组件更新前:ComponentWillUpdate");
}
// 组件更新完成
componentDidUpdate() {
console.log("组件更新完成:componentDidUpdate");
}
}
上述代码,我们通过this.forceUpdate()强制更新了页面,以此触发了componentWillUpdate、render、componentDidUpdat钩子
父组件触发render系列钩子
我们实现一个嵌套的父子组件,父组件有两个按钮,子组件用来显示父组件的选中按钮。
js
<script type="text/babel">
// 1、创建类组件
class Parent extends React.Component {
state = { text: "" };
choose = (type) => {
if (type === "1") this.setState({ text: "父组件选择了按钮一" });
if (type === "2") this.setState({ text: "父组件选择了按钮二" });
};
render() {
return (
<div>
<button
onClick={() => {
this.choose("1");
}}
>
按钮1
</button>
<button
onClick={() => {
this.choose("2");
}}
>
按钮2
</button>
<Child text={this.state.text}></Child>
</div>
);
}
}
// 渲染组件
class Child extends React.Component {
render() {
const { text } = this.props;
return <div>{text ? text : "什么都没选中"}</div>;
}
}
ReactDOM.render(<Parent />, document.getElementById("test"));
</script>
上述代码中我们在父组件中定义了按钮选中的状态文本值state.text,然后传递给子组件。子组件通过props接受。
当父组件切换按钮导致子组件props值发生变化时子组件触发一些列钩子
js
// 渲染组件
class Child extends React.Component {
// 父组件触发子组件props更新特有的钩子
componentWillReceiveProps() {
console.log("组件接受新的props前:componentWillReceiveProps");
}
// 控制是否允许组件更新的钩子,需要返回一个布尔值
shouldComponentUpdate() {
console.log("子组件状态更改控制钩子:shouldComponentUpdate");
return true;
}
// 组件更新前
componentWillUpdate() {
console.log("子组件更新前:ComponentWillUpdate");
}
// 组件更新完成
componentDidUpdate() {
console.log("子组件更新完成:componentDidUpdate");
}
render() {
console.log("子组件的render函数钩子");
const { text } = this.props;
return <div>{text ? text : "什么都没选中"}</div>;
}
}
如图,componentWillReceiveProps是子组件props值发生变化时,所特有的钩子。
钩子函数总结
React组件的生命周期可以分为三个阶段:
- 挂载阶段(Mounting):组件被创建并插入到DOM中
由ReactDom.render()触发--初次渲染
- constructor
- componnetWillMount
- render
- componentDidMount(常用)
这个钩子做一些初始化的事情,例如开启定时器、发送请求。订阅消息等
- 更新阶段(Updating):组件被重新渲染
- shouldComponentUpdate
- componentWillUpdate
- rende
- componentDidUpdate
- 卸载阶段(Unmounting):组件被从DOM中移除
- componnetWillUnmount
这个钩子做一些收尾的事情,例如关闭定时器、取消订阅消息等
React类组件中新版生命周期钩子详解
react的最新生命周期和旧版有一些区别,它删除了包含"will"关键词的钩子函数,增加了getDerivedStateFromProps和getSnapshotBeforeUpdate两个新钩子
getDerivedStateFromProps
getDerivedStateFromProps是React 16.3版本引入的一个新的生命周期钩子函数。它的作用是根据新的props和state来派生出一个新的state。这个生命周期函数在组件实例化、接收新的props、和父组件强制更新的时候都会调用。
它的用法类似于旧版的componentWillReceiveProps,但是getDerivedStateFromProps有两个参数:
- props:最新的props值
- state:当前组件的state
它的返回值应该是一个对象,代表着组件的新状态。如果不需要更新状态,则返回null。
这个函数在一定程度上能够减少由于componentWillReceiveProps导致的bug,因为它必须返回一个新的state,不能直接修改当前的state。这样做有一个好处,就是可以更好地控制状态的变化流程,增加组件的稳定性。
注:这个钩子几乎不使用
getSnapshotBeforeUpdate
getSnapshotBeforeUpdate 是 React 组件生命周期中的一个钩子函数,它在组件更新 DOM 之前调用,常常用于处理在组件更新后,需要获取 DOM 相关信息的情况。
在每次组件更新时,getSnapshotBeforeUpdate 都会被调用,并且在 render 方法之后被调用。其返回值可以作为 componentDidUpdate 方法的第三个参数传入,从而能够在 componentDidUpdate 中访问到前一时刻的 DOM 信息。
一个典型的 getSnapshotBeforeUpdate 方法的实现可能如下所示:
js
class ExampleComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
this.state = {
message: "",
};
}
getSnapshotBeforeUpdate(prevProps, prevState) {
// 计算更新前的 DOM 高度
const prevHeight = this.myRef.current.scrollHeight;
return prevHeight;
}
componentDidUpdate(prevProps, prevState, snapshot) {
// 对比更新前后的 DOM 高度,并更新其它相关状态
if (snapshot !== null) {
const currentHeight = this.myRef.current.scrollHeight;
if (currentHeight > snapshot) {
this.setState({ message: "Scrolling down" });
} else if (currentHeight < snapshot) {
this.setState({ message: "Scrolling up" });
} else {
this.setState({ message: "No scrolling" });
}
}
}
render() {
return (
<div ref={this.myRef}>
{this.props.children}
<p>{this.state.message}</p>
</div>
);
}
}
在上面的例子中,getSnapshotBeforeUpdate 方法计算了更新前的 DOM 高度并返回。在 componentDidUpdate 方法中,通过比较更新前后的高度,我们可以判断用户是向上滚动还是向下滚动,并更新相应的状态。
小结
生命周期函数是React组件中的一些方法,它们在组件的不同状态下被调用,可以在这些函数中实现一些特定的行为。React组件的生命周期可以分为三个阶段:
- 挂载阶段(Mounting):组件被创建并插入到DOM中
- 更新阶段(Updating):组件被重新渲染
- 卸载阶段(Unmounting):组件被从DOM中移除
下面是React中常用的生命周期函数:
- constructor(props):初始化组件的状态和属性。
- getDerivedStateFromProps(props, state):在组件更新前被调用。返回值将会被添加到组件的状态中。
- render():渲染组件。必须返回一个React元素或null。
- componentDidMount():组件已经被挂载到DOM中。可以在这个函数中发送请求。
- shouldComponentUpdate(nextProps, nextState):组件将要更新。可以在这个函数中判断是否需要更新组件。
- getSnapshotBeforeUpdate(prevProps, prevState):在组件更新前被调用。返回值会传递给componentDidUpdate()函数。
- componentDidUpdate(prevProps, prevState, snapshot):组件已经更新完毕。可以在这个函数中更新状态或者发送请求。
- componentWillUnmount():组件将要被卸载,可以在这个函数中清除定时器或者取消请求。