React组件在其应用生命周期中会经历不同的阶段,尽管幕后可能没有明显的迹象表明发生了任何事情。
这些阶段是
- 挂载
- 更新
- 卸载
- 错误处理
在每个阶段中都有一些方法,可以在该阶段对组件执行特定操作。例如,当从网络获取数据时,您需要在componentDidMount()
方法中调用处理API调用的函数,该方法在挂载阶段可用。
了解不同的生命周期方法对于React应用程序的开发非常重要,因为它允许我们在需要时精确地触发操作,而不会与其他操作混淆。我们将在本文中查看每个生命周期,包括可用的方法以及我们使用它们的场景类型。
挂载阶段
可以将挂载视为组件生命周期的初始阶段。在挂载发生之前,组件尚不存在——它仅仅是DOM眼中的一丝闪烁,直到挂载发生并将组件作为文档的一部分挂钩。
一旦组件挂载,我们就可以利用许多方法:constructor()
、render()
、componentDidMount()
和static getDerivedStateFromProps()
。每个方法都有其自身的用途,因此让我们按顺序查看它们。
constructor()
当直接在组件上设置状态以将方法绑定在一起时,需要使用constructor()
方法。以下是它的外观
// Once the input component is mounting...
constructor(props) {
// ...set some props on it...
super(props);
// ...which, in this case is a blank username...
this.state = {
username: ''
};
// ...and then bind with a method that handles a change to the input
this.handleInputChange = this.handleInputChange.bind(this);
}
需要注意的是,constructor是创建组件时调用的第一个方法。组件尚未渲染(即将到来),但DOM已经知道它,并且我们可以在它渲染之前挂钩到它。因此,这不是我们调用setState()
或引入任何副作用的地方,因为,好吧,组件仍然处于构造阶段!
我之前写过一篇关于ref的使用的教程,我注意到的一件事是在使用React.createRef()
时可以在constructor中设置ref。这是合理的,因为ref用于在没有props或不必使用更新值重新渲染组件的情况下更改值。
constructor(props) {
super(props);
this.state = {
username: ''
};
this.inputText = React.createRef();
}
render()
render()
方法是组件的标记在前端显示的地方。用户此时可以查看并访问它。如果您曾经创建过React组件,那么您已经熟悉它了——即使您没有意识到——因为它需要输出标记。
class App extends React.Component {
// When mounting is in progress, please render the following!
render() {
return (
<div>
<p>Hello World!</p>
</div>
)
}
}
但这并不是render()
的全部用途!它还可以用于渲染组件数组
class App extends React.Component {
render () {
return [
<h2>JavaScript Tools</h2>,
<Frontend />,
<Backend />
]
}
}
……甚至组件片段
class App extends React.Component {
render() {
return (
<React.Fragment>
<p>Hello World!</p>
</React.Fragment>
)
}
}
我们还可以使用它来渲染DOM层次结构之外的组件(类似于React Portal)
// We're creating a portal that allows the component to travel around the DOM
class Portal extends React.Component {
// First, we're creating a div element
constructor() {
super();
this.el = document.createElement("div");
}
// Once it mounts, let's append the component's children
componentDidMount = () => {
portalRoot.appendChild(this.el);
};
// If the component is removed from the DOM, then we'll remove the children, too
componentWillUnmount = () => {
portalRoot.removeChild(this.el);
};
// Ah, now we can render the component and its children where we want
render() {
const { children } = this.props;
return ReactDOM.createPortal(children, this.el);
}
}
当然,render()
可以——咳咳——渲染数字和字符串……
class App extends React.Component {
render () {
return "Hello World!"
}
}
……以及null
或布尔值
class App extends React.Component {
render () {
return null
}
}
componentDidMount()
componentDidMount()
名称是否泄露了它的含义?此方法在组件挂载后(即挂钩到DOM后)调用。在我之前关于在React中获取数据的教程中,您需要在这里向API发出请求以获取数据。
我们可以使用您的fetch方法
fetchUsers() {
fetch(`https://jsonplaceholder.typicode.com/users`)
.then(response => response.json())
.then(data =>
this.setState({
users: data,
isLoading: false,
})
)
.catch(error => this.setState({ error, isLoading: false }));
}
然后在componentDidMount()
钩子中调用该方法
componentDidMount() {
this.fetchUsers();
}
我们还可以添加事件监听器
componentDidMount() {
el.addEventListener()
}
很不错,对吧?
static getDerivedStateFromProps()
它的名字有点长,但static getDerivedStateFromProps()
并不像听起来那么复杂。它在挂载阶段的render()
方法之前以及更新阶段之前调用。它返回一个对象来更新组件的状态,或者在没有要更新的内容时返回null
。
为了理解它的工作原理,让我们实现一个计数器组件,该组件的counter
状态将具有某个值。仅当maxCount
的值较高时,此状态才会更新。maxCount
将从父组件传递。
这是父组件
class App extends React.Component {
constructor(props) {
super(props)
this.textInput = React.createRef();
this.state = {
value: 0
}
}
handleIncrement = e => {
e.preventDefault();
this.setState({ value: this.state.value + 1 })
};
handleDecrement = e => {
e.preventDefault();
this.setState({ value: this.state.value - 1 })
};
render() {
return (
<React.Fragment>
<section className="section">
<p>Max count: { this.state.value }</p>
<button
onClick={this.handleIncrement}
class="button is-grey">+</button>
<button
onClick={this.handleDecrement}
class="button is-dark">-</button>
</section>
<section className="section">
<Counter maxCount={this.state.value} />
</section>
</React.Fragment>
)
}
}
我们有一个按钮用于增加maxCount
的值,我们将其传递给Counter
组件。
class Counter extends React.Component {
state={
counter: 5
}
static getDerivedStateFromProps(nextProps, prevState) {
if (prevState.counter < nextProps.maxCount) {
return {
counter: nextProps.maxCount
};
}
return null;
}
render() {
return (
<div className="box">
<p>Count: {this.state.counter}</p>
</div>
)
}
}
在Counter
组件中,我们检查counter
是否小于maxCount
。如果是,我们将counter
设置为maxCount
的值。否则,我们什么也不做。
您可以使用下面的Pen进行操作,以查看它在前端是如何工作的
查看Pen
getDerivedStateFromProps by Kingsley Silas Chijioke (@kinsomicrote)
on CodePen.
更新阶段
当组件的props
或state
发生变化时,就会发生更新阶段。与挂载类似,更新也有自己的一套可用方法,我们接下来将介绍它们。也就是说,值得注意的是,render()
和getDerivedStateFromProps()
也会在此阶段触发。
shouldComponentUpdate()
当组件的state
或props
发生变化时,我们可以使用shouldComponentUpdate()
方法来控制组件是否应该更新。此方法在渲染发生之前以及接收状态和props时调用。默认行为为true
。要每次状态或props更改时都重新渲染,我们可以执行以下操作
shouldComponentUpdate(nextProps, nextState) {
return this.state.value !== nextState.value;
}
当返回false
时,组件不会更新,而是调用render()
方法来显示组件。
getSnapshotBeforeUpdate()
我们可以做的一件事是在某个时间点捕获组件的状态,这就是getSnapshotBeforeUpdate()
的设计目的。它在render()
之后但在任何新更改提交到DOM之前调用。返回值作为第三个参数传递给componentDidUpdate()
。
它将先前状态和props作为参数
getSnapshotBeforeUpdate(prevProps, prevState) {
// ...
}
至少根据我的经验,此方法的使用案例很少。它是那些您可能不会经常使用到的生命周期方法之一。
componentDidUpdate()
将componentDidUpdate()
添加到方法列表中,其中名称大致说明了一切。如果组件更新,那么我们可以在此时使用此方法挂钩到它,并将其传递给组件的先前props
和state
。
componentDidUpdate(prevProps, prevState) {
if (prevState.counter !== this.state.counter) {
// ...
}
}
如果您曾经使用过getSnapshotBeforeUpdate()
,您还可以将返回值作为参数传递给componentDidUpdate()
componentDidUpdate(prevProps, prevState, snapshot) {
if (prevState.counter !== this.state.counter) {
// ....
}
}
卸载阶段
这里我们基本上是在看安装阶段的反向操作。正如您可能预料的那样,当组件从 DOM 中移除且不再可用时,就会发生卸载 (unmounting)
。
这里我们只有一个方法:componentWillUnmount()
此方法在组件卸载并销毁之前被调用。在这里,我们希望在组件离开后执行任何必要的清理工作,例如移除可能在componentDidMount()
中添加的事件监听器,或清除订阅。
// Remove event listener
componentWillUnmount() {
el.removeEventListener()
}
错误处理阶段
组件中可能会出现错误,这会导致错误。我们已经有一段时间使用错误边界来帮助解决这个问题。这个错误边界组件利用了一些方法来帮助我们处理可能遇到的错误。
getDerivedStateFromError()
我们使用getDerivedStateFromError()
来捕获从子组件抛出的任何错误,然后我们使用它来更新组件的状态。
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false
};
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return (
<h1>Oops, something went wrong :(</h1>
);
}
return this.props.children;
}
}
在这个例子中,当子组件抛出错误时,ErrorBoundary
组件将显示“Oops, something went wrong”。我们在这篇文章中提供了更多关于此方法的信息:React 16.6.0 中发布的功能总结。
componentDidCatch()
虽然getDerivedStateFromError()
适用于在发生副作用(如错误记录)的情况下更新组件的状态,但我们应该使用componentDidCatch()
,因为它在提交阶段(DOM 已更新)被调用。
componentDidCatch(error, info) {
// Log error to service
}
getDerivedStateFromError()
和componentDidCatch()
都可以在ErrorBoundary
组件中使用。
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false
};
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, info) {
// Log error to service
}
render() {
if (this.state.hasError) {
return (
<h1>Oops, something went wrong :(</h1>
);
}
return this.props.children;
}
}
这就是 React 组件的生命周期!
了解 React 组件如何与 DOM 交互是一件很酷的事情。很容易认为一些“魔法”发生了,然后页面上就会出现一些东西。但是 React 组件的生命周期表明,这种混乱是有秩序的,并且它旨在赋予我们极大的控制力,使我们能够从组件进入 DOM 到离开 DOM 的整个过程中都能控制事情的发生。
我们在相对较短的空间内涵盖了很多内容,但希望这能让你很好地了解 React 如何处理组件,以及在处理过程的各个阶段我们拥有哪些能力。如果你对我们在这里讨论的任何内容不清楚,请随时提出任何问题,我将尽我所能提供帮助!
Yo dawg,关于生命周期事件的文章写得非常好。
你在日常工作中使用
getDerivedStateFromProps()
吗?我在工作中从来没有找到它的用武之地。。render() 示例的第 4 行存在语法错误
溜了溜了!
哇,解释得非常好。点赞
这很有趣,但有点过时了,因为 hooks 简化了所有这些逻辑,这些逻辑将在 React 17 中被移除。