生命周期钩子
关于生命周期钩子,官方文档给出了详细的说明
不同阶段调用的生命周期钩子
挂载阶段
当组件实例被创建并插入 DOM 中时,其生命周期调用顺序如下:
constructor()
static getDerivedStateFromProps()
render()
componentDidMount()
更新阶段
当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用顺序如下:
static getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate()
卸载阶段
当组件从 DOM 中移除时会调用如下方法:
componentWillUnmount()
生命周期钩子函数介绍
constructor(props)
构造函数,创建实例时候会调用,通常用于绑定函数,初始化state。
static getDerivedStateFromProps(props, state)
静态方法,用于state的某些值只依赖于当前的props的场景,并且在初始挂载及后续更新时都会被调用
render()
渲染函数,返回JSX或者React.element。
componentDidMount()
挂载完成,通常在这个钩子中做初始化操作,请求接口、绑定事件、启动定时器等。
shouldComponentUpdate()
用于优化性能,判断是否需要比较更新。
getSnapshotBeforeUpdate(prevProps, prevState)
它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期的任何返回值将作为参数传递给 componentDidUpdate()。
componentDidUpdate(prevProps, prevState, snapshot)
更新完成,做一些有副作用的操作,例如请求接口,弹出提示等等。
prevProps和prevState是更新前的props和state,用于逻辑判断中需要对比更新的改动。
componentWillUnmount()
会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,如解除订阅、清除定时器。
getDerivedStateFromProps和getSnapshotBeforeUpdate示例
【前端面试刷题网站:灵题库,收集大厂面试真题,相关知识点详细解析。】
对于getDerivedStateFromProps和getSnapshotBeforeUpdate不是很容易理解,给出示例代码:
getDerivedStateFromProps对props传入的digit和Child的state的sum计算求和。
注意getDerivedStateFromProps是静态方法,因此没有this。getDerivedStateFromProps的作用就是根据props和state计算新的state,不需要this。
import React from 'react';
class Child extends React.PureComponent {
// state完全依赖于props.digit,因此可以使用getDerivedStateFromProps来进行处理
static getDerivedStateFromProps(props, state) {
return {
sum: state.sum + props.digit
};
}
state = {
sum: 0
};
render() {
return (
<div>累计:{this.state.sum}</div>
);
}
}
class App extends React.PureComponent {
state = {
digit: 0
};
onButtonClick = () => {
this.setState(preState => ({
digit: preState.digit + 1
}));
};
render() {
return (
<div>
数字:{this.state.digit}
<button onClick={this.onButtonClick}>+1</button>
<Child digit={this.state.digit} />
</div>
);
}
}
export default App;
对getSnapshotBeforeUpdate的官方文档的示例代码稍加改造。
App有个按钮元素,点击则给list增加元素,ScrollingList组件渲染list,并且如果列表处于底部时候,增加新元素后会自动滚动到底部。
import React from 'react';
import './App.css';
class ScrollingList extends React.Component {
constructor(props) {
super(props);
this.listRef = React.createRef();
}
getSnapshotBeforeUpdate(prevProps, prevState) {
// 如果有增加的item,则可能需要滚动
if (prevProps.list.length < this.props.list.length) {
const list = this.listRef.current;
// 如果当前没有在回看,需要滚动
return list.scrollHeight - list.scrollTop - list.clientHeight === 0;
}
return false;
}
componentDidUpdate(prevProps, prevState, snapshot) {
// 如果我们 snapshot 有值,说明我们刚刚添加了新的 items,
// 并且没有在会看,说明我们需要滚动到底部
//(这里的 snapshot 是 getSnapshotBeforeUpdate 的返回值)
if (snapshot) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight;
}
}
render() {
return (
<div ref={this.listRef} style={
{border: '1px solid', height: 100, overflow: 'auto', marginTop: 50}}>
{
this.props.list.map(text => (
<li key={text} style={
{fontSize: 20}}>{text}</li>
))
}
</div>
);
}
}
class App extends React.PureComponent {
state = {
list: []
};
onButtonClick = () => {
this.setState(preState => ({
list: [...preState.list, preState.list.length]
}));
};
render() {
return (
<div>
<button onClick={this.onButtonClick}>添加</button>
<ScrollingList list={this.state.list} />
</div>
);
}
}
export default App;
React16更新的生命周期钩子
移除的
UNSAFE_componentWillMount()
UNSAFE_componentWillReceiveProps(nextProps)
UNSAFE_componentWillUpdate(nextProps, nextState)
新增的
static getDerivedStateFromProps(props, state)
getSnapshotBeforeUpdate(prevProps, prevState)
关于setState
setState是异步的吗?
出于性能考虑,React将setState进行异步处理,即调用setState时候不会马上更新,而是将setState的值放入一个队列,然后延时批量处理队列中的state。在生命周期钩子和React合成事件中中调用setState,会将数据添加到一个队列中,合并处理,这时候setState是异步的。而在其他的React控制之外的地方调用(比如原生dom事件)则不是异步的。
setState异步特性,导致需要注意两个问题
- 设置完state,获取数据的时机
由于异步特性,setState改变state数据后,无法马上获取到state的最新值。
由于setState是异步的,如果想获取改变后的值,应该在setState的第二个参数回调函数中访问state的最新值。
- 修改数据依赖现有的数据
由于setState会异步合并处理,因此如果设置的state值依赖于之前的state值,需要给setState传一个函数,
函数参数是之前的state,返回的值是新的改变的state属性。
import React from 'react';
class App extends React.Component {
state = {
digit: 1,
text: 'a'
};
componentDidMount() {
document.getElementById('btn')
.addEventListener('click', this.onAddButtonClick);
}
onAddButtonClick = () => {
// bad
// const add1 = () => {
// this.setState({digit: this.state.digit + 1});
// };
// add1();
// add1();
// add1();
// good
const add1 = () => {
this.setState((state) => {
return {digit: state.digit + 1};
});
};
add1();
add1();
add1();
};
onAddButtonClick2 = () => {
// work
const add1 = () => {
this.setState({digit: this.state.digit + 1});
};
add1();
add1();
add1();
};
onChangeButtonClick = () => {
// bad
// this.setState({text: 'b'});
// console.log(this.state.text);
// good
this.setState({text: 'b'}, () => {
console.log(this.state.text);
});
};
render() {
return (
<div>
<div>{this.state.digit}</div>
<button onClick={this.onAddButtonClick}>+3</button>
<button id="btn">+3(原生事件)</button>
<hr />
<div>{this.state.text}</div>
<button onClick={this.onChangeButtonClick}>修改文本</button>
</div>
);
}
}
export default App;
React更新界面的主要过程可以简单描述为,调用setState之后,React会更新state,然后调用组件的render得到新的state对应的虚拟dom,然后对比当前的和更新后端虚拟dom,判断是否需要更新dom,以及如何更新dom。
import React from 'react';
import './App.css';
class App extends React.Component {
state = {
di