React组件的生命周期分为三个阶段:
-
组件初始化 Mounting(也可以mount这个单词翻译为组件安装)
-
组件运行时 Updating
-
组件卸载时 Unmounting
Mounting — 组件初始化
-
constructor():组件被创建时,首先被调用的方法,通常用来初始化组件state以及绑定事件处理方法,
该方法中不能调用setState()
,该构造方法接受一个props参数,必须在方法中首先调用super(props)才能保证props被传入组件中。 -
componentWillMount():组件渲染之前调用,只会被调用一次。
在这个方法里setState不生效,不会触发re-render,而是会进行state合并。
实际开发中很少用这个方法,因为都可以放到constructor()中来处理,而且新版react已经弃用并认为该方法是legacy(老式的)不安全的。 -
render():这是定义组件时唯一必要的方法,负责渲染真正的DOM。它是个纯函数,不能用来执行任何有副作用的操作,
所以不能在render中执行setState
,这会改变组件的状态。 -
componentDidMount():组件渲染后调用,只会被调用一次,这个时候组件已经被挂载,组件已经生成对应的DOM结构,需要操作DOM可以在这个时期。同时也是从远端取数据,发送ajax请求,设置监听,定时任务的好地方。
这里可以setState。
Updating — 组件运行时
-
componentWillReceiveProps(nextProps):只会在外部props变化时才会调用,state变化不会调用。
这里可以setState(),但是不会触发re-render,而是会进行state合并。
方法的参数nextProps是父组件传递给当前组件的新的props,但是父组件的render方法并不能保证传给子组件的props发生变化,也就是说nextProps可能和当前props的值相等,所以需要比较props是否变化再setState,否则可能会发生多次调用。 -
shouldComponentUpdate(nextProps,nextState):该方法决定组件是否继续执行更新过程,返回布尔值。一但返回false,后续方法都不再执行,组件但更新过程终止。一般通过比较nextProps,nextState和当前props,state决定方法的返回值。该方法可以用来减少组件不必要的渲染,从而优化组件的性能。
不能调用setState
,否则会引起循环调用问题,render永远无法调用,组件也无法正常渲染。在使用forceUpdate时不被调用。
-
componentWillUpdate(nextProps,nextState):该方法在组件render之前调用,可以做为组件更新前执行某些工作的地方,很少用到且新版react已经弃用。
不能调用setState
,否则会引起循环调用问题,render永远无法调用,组件也无法正常渲染。这个方法可以被componentDidUpdate()替代。在使用forceUpdate的时候可以调用。
-
componentDidUpdate(prevProps,prevState):组件更新后被调用,可以做为操作更新后DOM的地方,
可以调用setState。
Unmounting — 组件卸载时
componentWillUnmount():该方法在组件卸载之前调用,可以在这里执行一些清理工作,如清楚组件中的定时器,取消某些网络请求,清除componentDidUpdate手动创建的DOM元素等,以避免引起内存泄露。
二、Effect Hook (effect翻译是影响的意思,这边改叫:副作用的特殊函数)
它可以在函数组件中执行副作用的操作,这个副作用(即Effect Hook)相当于React组件生命周期componentDidMount
,componentDidUpdate
和 componentWillUnmount
这三个函数的组合.
哪些算副作用:数据获取,设置订阅以及手动更改 React 组件中的 DOM 都属于副作用。
例 子:
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => { // Update the document title using the browser API
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
三、React组件有两种副作用:无需要清除的effct和需要清除的effct 其实effect Hook 使用同一个 API 来满足这两种情况。
(一)无需要清除的effct(即有一些副作用不需要清除)
我们经常需要在React更新Dom后运行一些额外的代码(例如发送网络请求,和动变更Dom,记录日志等),即我们内心希望组件每次渲染后均执行一段代码,但React没有这样的方法,只提供了
组件加载和更新的两个方法来满足要求,即:componentDidMount和componentDidUpdate,所以经常写代码需要在这两个方法均要有才能满足需求。
useEffect的工作原理:
通过使用这个 Hook,你可以告诉 React 组件需要在渲染后执行某些操作。React 会保存你传递的函数(我们将它称之为 “effect”),并且在执行 DOM 更新之后调用它。在这个 effect 中,我们设置了 document 的 title 属性,不过我们也可以执行数据获取或调用其他命令式的 API。
useEffect
会在每次渲染后都执行: 默认情况下,它在第一次渲染之后和每次更新之后都会执行。你可能会更容易接受 effect 发生在“渲染之后”这种概念,不用再去考虑“挂载”还是“更新”。React 保证了每次运行 effect 的同时,DOM 都已经更新完毕。
经验丰富的 JavaScript 开发人员可能会注意到,传递给 useEffect
的函数在每次渲染中都会有所不同,这是刻意为之的。事实上这正是我们可以在 effect 中获取最新的 count
的值,而不用担心其过期的原因。每次我们重新渲染,都会生成新的 effect,替换掉之前的。某种意义上讲,effect 更像是渲染结果的一部分 —— 每个 effect “属于”一次特定的渲染。
(二)需要清除的effct(即有一些副作用需要清除)
原来React一般在componentDidMount
和 componentWillUnmount
之间相互对应来完成副作用的清除动作,现在改用effect hook,例如:
import React, { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
// Specify how to clean up after this effect:
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
effect只要定义一个返回函数,它就可以实现副作用的清除,如下面:
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
为什么要在 effect 中返回一个函数? 这是 effect 可选的清除机制。每个 effect 都可以返回一个清除函数。如此可以将添加和移除订阅的逻辑放在一起。它们都属于 effect 的一部分。
注意:并不是必须为 effect 中返回的函数命名。这里我们将其命名为 cleanup
是为了表明此函数的目的,但其实也可以返回一个箭头函数或者给起一个别的名字,例如:
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
其他的 effect 可能不必清除,所以不需要返回。
useEffect(() => {
document.title = `You clicked ${count} times`;
});
四、多个Effect Hook的定义,实现关注点的分离,即把不相关的逻辑分离到不同的effect中。
五、Effect Hook的特点:每一次组件渲染均会执行( 如何跳过effect进行性能优化)
这样可以避免原来React组件老忘了处理componentDidUpdate而产生的bug。但每次渲染均要执行,也会影响性能,如何做呢:
这是很常见的需求,所以它被内置到了 useEffect
的 Hook API 中。如果某些特定值在两次重渲染之间没有发生变化,你可以通知 React 跳过对 effect 的调用,只要传递数组作为 useEffect
的第二个可选参数即可:
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新
对于有清除操作的 effect 同样适用:
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
}, [props.friend.id]); // 仅在 props.friend.id 发生变化时,重新订阅
如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([]
)作为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。这并不属于特殊情况 —— 它依然遵循依赖数组的工作方式。
除此之外,请记得 React 会等待浏览器完成画面渲染之后才会延迟调用 useEffect
,因此会使得额外操作很方便。
这就需要给effect这个函数传递第二个参数(值数组),第一个参数是函数,例如:
useEffect(
() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
},
[props.source],
);