Class 中的生命周期
这里主要讲述react16.4之后的生命周期,可以分成三个阶段:
Mounting 阶段
这个阶段涉及到组件的初始化和第一次渲染。
constructor():组件实例化时调用
绑定事件处理函数、比如绑定 this , 节流,防抖等。
getDerivedStateFromProps(nextProps,prevState):在 render() 方法之前调用。(componentWillMount被这个代替了)
返回一个对象去更新state,可作为shouldComponentUpdate 第二个参数 newState。
render():渲染组件,必须实现。
createElement创建元素 , cloneElement 克隆元素 ,React.children 遍历 children 的操作。
componentDidMount():在组件第一次渲染后调用,用于执行一些初始化操作。
Updating 阶段
这个阶段涉及到组件被重新渲染的情况,如 props 或 state 的改变。
getDerivedStateFromProps():同上。(componentWillReceiveProps被这个代替了,componentWillUpdate也可以理解被这个代代替是在shouldComponentUpdate之后执行的) shouldComponentUpdate(newProps,newState,nextContext):在 render() 方法之前调用,用于确定组件是否需要重新渲染。
返回true
则组件重新渲染,返回false则组件不渲染
render():同上。
getSnapshotBeforeUpdate(prevProps,preState) :在 render() 方法之后、更新 DOM 之前调。(代替了componentWillUpdate)
用于获取当前 DOM 的快照,比如 DOM 元素位置等。
将返回一个值作为一个snapShot,传递给 componentDidUpdate作为第三个参数。
componentDidUpdate(prevProps, prevState, snapshot):在组件更新后调用,可以获取 DOM 最新状态。
Unmounting 阶段
这个阶段涉及到组件被卸载的情况。
componentWillUnmount():在组件被卸载前调用。
Hook生命周期
useInsertionEffect
DOM 更新之前执行。
解决 CSS-in-JS 在渲染中注入样式的性能问题,例如在useLayoutEffect 动态生成 style 标签,可能会导致浏览器再次重回和重排。
useLayoutEffect
在 DOM 更新之后,浏览器绘制之前同步执行(requestAnimationFrame)。
function doSomethingBeforePaint() {
// 执行需要在绘制之前完成的操作
}
// requestAnimationFrame可以让浏览器在下一次绘制之前执行指定的回调函数
requestAnimationFrame(doSomethingBeforePaint);
适合操作DOM,改变布局。通常用于执行一些需要更新页面的操作,例如更新动画或调整元素位置和大小等。
useEffect
用于在组件渲染后执行一些操作,可以模拟 componentDidMount、componentDidUpdate 和 componentWillUnmount 生命周期。
第一个参数 callback: callback 和 setTimeout
回调函数一样,放入任务队列,等到主线程任务完成,DOM 更新,js 执行完成,视图绘制完毕,才执行。
组件渲染完成后异步执行,不会阻塞浏览器的渲染进程。
不推荐DOM 操作,浏览器再次回流和重绘,视图上可能会造成闪现突兀的效果。
第一个参数 callback返回方法destory:下一次callback执行前执行或者组件卸载前执行。
第二个参数作为依赖项:是一个数组,可以有多个依赖项。
依赖项改变,执行上一次callback返回值destory方法,然后执行callback。
当依赖为[]时,初始化执行一次。当没有依赖时,每一次执行函数组件,都会执行该 effect。
Demo详解
import React, { Component } from 'react';
// 创建阶段
// 更新阶段
// 卸载阶段
// 创建阶段
// 创建阶段主要分成了以下几个生命周期方法:
// constructor
// getDerivedStateFromProps
// render
// componentDidMount
// 更新阶段
// 该阶段的函数主要为如下方法:
// getDerivedStateFromProps
// shouldComponentUpdate
// render
// getSnapshotBeforeUpdate
// componentDidUpdate
// 卸载阶段
// componentWillUnmount
interface ExampleProps {
name: string;
}
interface ExampleState {
count: number;
}
class Example extends Component<ExampleProps, ExampleState> {
constructor(props: ExampleProps) {
super(props);
console.log(
`实例过程中自动调用的方法,在方法内部通过super关键字获取来自父组件的props
constructor是ES6中的一个构造函数,在组件实例化的时候被调用。它用于初始化组件的state和绑定事件处理函数等操作。
注意:在constructor中调用setState是不安全的,因为在这个阶段React还没有生成虚拟DOM,调用setState不会触发重新渲染,但是会直接修改state`,
'constructor',
);
this.state = {
count: 0,
};
}
static getDerivedStateFromProps(props: ExampleProps, state: ExampleState) {
console.log(
`该方法是新增的生命周期方法,是一个静态的方法,因此不能访问到组件的实例
执行时机:组件创建和更新阶段,不论是props变化还是state变化,也会调用
在每次render方法前调用,第一个参数为即将更新的props,第二个参数为上一个状态的state,可以比较props 和 state来加一些限制条件,防止无用的state更新
该方法需要返回一个新的对象作为新的state或者返回null表示state状态不需要更新,在该方法中,我们可以对props和state进行比较,加入一些限制条件,避免无用的state更新`,
'getDerivedStateFromProps',
);
return null;
}
shouldComponentUpdate(nextProps: ExampleProps, nextState: ExampleState) {
console.log(
`用于告知组件本身基于当前的props和state是否需要重新渲染组件,默认情况返回true
执行时机:到新的props或者state时都会调用,通过返回true或者false告知组件更新与否
一般情况,不建议在该周期方法中进行深层比较,会影响效率
同时也不能调用setState,否则会导致无限循环调用更新`,
'shouldComponentUpdate',
);
return nextState.count % 2 === 0;
}
componentDidMount() {
console.log(
`组件挂载到真实DOM节点后执行,其在render方法之后执行
此方法多用于执行一些数据获取,初始化一些状态,事件监听等操作
`,
'componentDidMount',
);
}
getSnapshotBeforeUpdate(prevProps: ExampleProps, prevState: ExampleState) {
console.log(
`该周期函数在render后执行,执行之时DOM元素还没有被更新
该方法返回的一个Snapshot值,作为componentDidUpdate第三个参数传入
此方法的目的在于获取组件更新前的一些信息,比如组件的滚动位置之类的,在组件更新后可以根据这些信息恢复一些UI视觉上的状态
`,
'getSnapshotBeforeUpdate',
);
return 'from getSnapshotBeforeUpdate';
}
componentDidUpdate(
prevProps: ExampleProps,
prevState: ExampleState,
snapshot: any,
) {
console.log(
`执行时机:组件更新结束后触发 在该方法中,可以根据前后的props和state的变化做相应的操作,如获取数据,修改DOM样式等`,
'componentDidUpdate',
);
}
componentWillUnmount() {
console.log(
`此方法用于组件卸载前,清理一些注册是监听事件,或者取消订阅的网络请求等
一旦一个组件实例被卸载,其不会被再次挂载,而只可能是被重新创建
`,
'componentWillUnmount',
);
}
handleClick = () => {
this.setState((prevState) => ({ count: prevState.count + 1 }));
};
render() {
console.log(
`生成虚拟DOM对象,返回该组件的虚拟DOM树,类组件必须实现的方法,用于渲染DOM结构,可以访问组件state与prop属性
注意: 不要在 render 里面 setState, 否则会触发死循环导致内存崩溃`,
'render',
);
const { name } = this.props;
const { count } = this.state;
return (
<div>
<h1>Hello, {name}!</h1>
<p>You clicked the button {count} times.</p>
<button onClick={this.handleClick}>Click me</button>
</div>
);
}
}
export default Example;
在使用 React Hooks 的函数组件中,没有生命周期方法,但是可以使用一些与生命周期相关的 Hooks,如 useEffect、useLayoutEffect、useMemo 等。下面是一个示例组件,演示了 React Hooks 中的生命周期相关 Hooks 和它们的顺序:
import React, { useState, useEffect, useLayoutEffect, useMemo } from "react";
function HooksLifecycleDemo() {
const [count, setCount] = useState(0);
console.log("render");
useEffect(() => {
console.log("componentDidMount");
return () => console.log("componentWillUnmount");
}, []);
useEffect(() => {
console.log("componentDidUpdate");
return () => console.log("cleanup");
}, [count]);
useLayoutEffect(() => {
console.log("useLayoutEffect");
}, [count]);
const memoizedValue = useMemo(() => {
console.log("useMemo");
return count * 2;
}, [count]);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<p>Memoized value: {memoizedValue}</p>
</div>
);
}
export default HooksLifecycleDemo;
上述组件包含了以下 Hooks:
useState: 用于定义组件的状态。 useEffect: 用于在组件渲染后执行一些操作,可以模拟 componentDidMount、componentDidUpdate 和 componentWillUnmount 生命周期。 useLayoutEffect: 与 useEffect 类似,但会在浏览器绘制之前同步执行。 useMemo: 用于缓存计算结果,避免重复计算,类似于 shouldComponentUpdate 生命周期。 我们可以通过在控制台打印各个 Hooks 的名称,来查看它们的顺序和调用次数。注意,由于 Hooks 可能会被多次调用,因此它们可能会在不同的时间调用多次,而不像生命周期方法那样只调用一次。