玩转 React(五)- 组件的内部状态和生命周期
文章标题总算是可以正常一点了……
通过之前的文章我们已经知道:在 React 体系中所谓的 "在 JavaScript 中编写 HTML 代码" 指的是 React 扩展了 JavaScript 的语法,也就是 JSX。JSX 语法中可以以类似 HTML 语法的方式使用 React 组件,从而编写 React 组件就有一种创造一个新的 HTML 标签的体验。
上一篇文章《玩转 React(四)- 创造一个新的 HTML 标签》介绍了如何来创建一个 React 组件,以及组件的属性。了解到组件的视图是属性的映射,通过改变组件属性可以触发组件重新渲染,从而改变组件的视图。其实组件的视图并不仅仅是由属性映射来的,本篇将介绍另一种可以触发组件重新渲染的方式,即组件的内部状态(state),严格来说组件的视图是由属性和内部状态映射而来的,即:view = f(props, state)
,跟属性类似,状态的改变也会触发组件重新渲染,只不过状态是组件内部基于自身逻辑或者用户事件自己维护的,而不是由外部输入的。
另外本文中会介绍一个通过类继承方式定义的组件的生命周期,以及在各个生命周期函数中能做什么,不能或尽量不要做什么。
内容摘要
ReactDOM.render
在一个单页面 web 应用中通常只调用一次。组件可以通过
setState
改变内部状态state
来更新视图。setState
多数情况下是异步的。不要直接使用当前
state
的值生成下一个state
。不要直接通过
this.state
修改state
。组件生命周期流程图。
各个生命周期函数介绍及使用经验。
以上是本文的内容摘要,如果你已经知道我要说的是什么,那么就没有必要继续看下去了,节约时间。
组件的内部状态
此前,我们已经了解到可以通过 ReactDOM.render(<HelloMessage name="Lucy" />, container)
的方式,将带有特定属性的组件渲染到页面的某个 DOM 节点中(container),这样页面上会展示出 “Hello Lucy”,当我们希望页面上展示 “Hello Tom” 的时候,我们可以将组件的 name 属性改为 Tom 后再次调用 ReactDOM.render
方法,这样组件就会以新的属性重新渲染,从而更新组件的视图。
下面是官方文档中一个展示时钟的例子,我简单改造了下:
https://codepen.io/Sarike/pen...
例子中定义了一个 Clock
组件,组件接收一个 time
属性,在组件外部通过 setInterval
周期性地调用 ReactDOM.render
不断更新 Clock
的属性并重新渲染。
然而在很多实际场景中,对于一个时钟组件,我们希望它有更好的封装性和复用性,也就是说我们希望只调用一次 ReactDOM.render(<Clock />, container)
然后它可以自己更新自己的视图,这样我们就更容易在页面上放置多个时钟了,即复用性更好了。
要达到这个目的,就需要组件的内部状态来支持。组件有一个特殊的属性 state
用来保存组件的内部状态。用户可以通过 this.setState(statePatch)
来更新组件的状态,组件的状态更新后会重新执行 render
方法来更新视图,上面的例子使用内部状态改造后:
https://codepen.io/Sarike/pen...
这样 Clock
作为一个完整的时钟组件就可以自己来更新自己了,上篇文中也有提到过,如果想要使用组件的内部状态,那组件必须以类继承的方式来定义,而不能使用函数式组件。所以说,函数式组件经常也被称作是无状态组件(stateless)。
上面例子中有用到 componentDidMount
和 componentWillUnmount
两个函数,它们是组件的生命周期函数,本文的后半部分将会介绍,这俩函数分别在组件挂载到页面上和组件将要从页面上移除时调用。
改造后的例子,我们只需要调用一次 ReactDOM.render
即可,在实际的项目中,一个完整的单页面 web 应用,也只需要调用一次 ReactDOM.render
方法把根组件挂载到页面中即可,剩下的工作就都放心地交给 React 就行了。
初始化组件内部状态
在创建一个拥有内部状态的组件时,我们需要对内部状态进行初始化,即设置组件最初的状态是什么。做法很简单,就是在构造函数 constructor
中设置 state
属性就可以了。如下所示:
class MyComponent extends React.Component {
constructor(props) {
super(props); // 这行代码不能少哦
this.state = {
name: "Lucy"
}
}
}
setState 大多数情况下是异步的
setState
多数情况下是异步的,异步意味着通过 setState
更新组件状态后,不能立刻通过 this.state
来获取到更新之后的值,另外当连续多次调用 setState
来更新同一个字段时,只有最后一次更新才会生效。如下示例:
https://codepen.io/Sarike/pen...
如果希望上面示例代码正常工作,你需要通过回调函数的方式来生成下一个 state,如下所示:
this.setState(preState => ({value: preState.value + 1}));
this.setState(preState => ({value: preState.value + 2}));
this.setState(preState => ({value: preState.value + 3}));
所以,直接基于当前 state
的值,生成一下个 state
是不靠谱的,但是很多不清楚这一点的同学基本上都是这么做的,因为写起来简单嘛,而且貌似也没有什么问题。这是因为很多情况下,业务逻辑没有那么复杂,基本不会频繁调用 setState
。但是这确实是一个隐患,如果在项目初期不注意规避,等项目复杂到一定程度以后,可能会出现难以排查的BUG。
那为什么说多数情况下是异步的呢?难道有些情况下不是异步的吗?是的,实际上只有在 React 能控制的事件处理过程中调用的 setState
才是异步的,如:生命周期函数,React 内置的如 button,input 等组件的事件处理函数。在多数的情况下我们只需要在这些地方控制我们的组件就够了,所以说大多数情况下 setState
是异步的。
在某些特殊的组件中,可能需要通过 addEventListener
来设置某些 DOM 的事件处理函数,在这种通过原生的 JS API 来设置的事件处理过程调用 setState
就是同步的,会立即更新 this.state
。另外还有 setInterval
、setTimeout
等原生 API 的回调函数也是如此。
参考:https://www.zhihu.com/questio...
不要直接通过 this.state 来更新组件状态
这一点跟属性类似,直接通过 this.state
修改组件状态,组件状态被修改了,但并不会触发组件的重新渲染。这样就会导致组件视图与状态不一致。
生命周期函数
一个组件被我们创造到这个世界上之后,在使用它时,它的每个实例都是有一定生命周期的,下面这张图说明了一个组件实例的生命周期:
图片来源:https://tylermcginnis.com/an-...,这张图略微有点老,不过结合下文来看也没什么问题。
下面我们来解释一下上面这张图。
组件初始化:constructor
我们定义的每一个组件,都是一个类(class),这些类被实例化后才能作为 React DOM 中的一个节点渲染到页面上。所以,当我们通过 ReactDOM.render
或者在某个组件中通过 JSX 表达式将一个组件第一次渲染到页面上时,组件首先要做的就是对组件进行实例化。
实例化主要做的事情:
创建一个组件的实例对象(也就是 Element,通常对应一个JSX表达式,如:
<MyComponent />
)。获取组件的默认属性。
获取组件的初始内部状态(在
constructor
中this.state = xxxx;
)。
componentWillMount
在组件被渲染到页面上之前执行,在组件的整个生命周期内只执行一次。在这里可以调用 setState
更新内部状态,但是更推荐将这里的状态更新操作放到 constructor
中。
该函数执行完后会立马执行 render
方法并将组件渲染到页面上。所以,在这里执行 setState 不会触发额外的渲染过程,因为这是没有必要的。
componentDidMount
组件被渲染到页面上后立马执行,在组件的整个生命周期内只执行一次。这个时候是做如下操作的好时机:
某些依赖组件 DOM 节点的操作。
发起网络请求。
设置
setInterval
、setTimeout
等计时器操作。
在这里可以调用 setState
更新组件内部状态,且会触发一个重新渲染的过程,即会重新执行 render
方法并更新视图。
componentWillReceiveProps
componentWillReceiveProps(nextProps)
该声明周期函数可能在两种情况下被调用:
组件接收到了新的属性。新的属性会通过
nextProps
获取到。组件没有收到新的属性,但是由于父组件重新渲染导致当前组件也被重新渲染。
你只要知道,当该函数被调用时,并不一定是因为属性发生了变化。
在这里也可以调用 setState
更新组件的内部状态,同样也不会触发额外的重新渲染操作,React 会聪明地用更新后的属性和内部状态进行一次重新渲染。
shouldComponentUpdate
shouldComponentUpdate(nextProps, nextState)
这是一个询问式的生命周期函数,所以该函数需要一个返回值 true/false
,如果为 true
,组件将触发重新渲染过程,如果为 false
组件将不会触发重新渲染。因此,合理地利用该函数可以一定程度节省开销,提高系统的性能。
此处不能调用 setState
更新组件的状态。
由于组件属性或者内部状态被改变时都触发组件重新渲染,所以该函数接受两个参数:新的属性(nextProps)、新的状态(nextState)。
在处理该声明周期函数时,切记要兼顾属性和状态,不能只顾其一,不然很容易踩坑。例如:某位同学只依据属性来判断是否触发重新渲染,而忽略了内部状态,这样就导致你无论如何 setState
,组件视图都不能正常更新。
在上篇文章中我们提到类继承方式定义组件时说到,React 提供了两个基类,一个是 Component
,另一个是 PureComponent
,两者的差别就在于后者已经帮我们简单实现了一下 shouldComponentUpdate
函数,当属性和状态都没有发生变化时返回 false
以避免额外的开销。
但是比对过程出于性能考虑,只是进行浅比对,也就是只比对对象的第一级字段,而且是否发生变化是通过 Object.is 方法类判断的。所以会导致有时候发生变化了组件没有更新,没有变化却触发了重新渲染过程。这个在这里不再赘述,想深入探讨可以扫描问候的二维码加我微信好友(我的微信:leobaba88)。
componentWillUpdate
当组件 shouldComponentUpdate
返回 true
或者调用 forceUpdate
时将触发此函数。
该函数中不能调用 setState
更新组件状态,当你想这么做的时候,你可以考虑将它移到 componentWillReceiveProps
函数里。
该函数在函数第一次渲染的时候不会执行。
componentDidUpdate
componentDidUpdate(prevProps, prevState)
在组件重新渲染过程中,重新执行 render
方法并更新组件视图后立即执行该函数。类似组件第一次渲染过程中的 componentDidMount
,该函数在第一次渲染时不会执行。
在此处是做这些事情的好时机:
执行依赖新 DOM 节点的操作。
依据新的属性发起新的网络请求。(但是此处一定要格外谨慎,一定要在确认属性变化后再发起网络请求,不然极有可能进入死循环:didUpdate -> ajax -> changeProps -> didUpdate -> ...)。
componentWillUnmount
当组件被从页面中移除之前调用,此时是清理战场的好时机,如清理定时器、终止网络请求等。
componentDidCatch
componentDidCatch(error, info)
这是 React 16 新加入的一个生命周期函数。定义该生命周期函数的组件将会成为一个错误边界,错误边界这个词非常形象,它可以有效地将错误限制在一个有限的范围内,而不会导致整个应用崩溃,防止一颗耗子屎坏了一锅汤。
错误边界组件,可以捕获其整个子组件树内发生的任何异常,但是却不能捕获自身的异常。
下面是官方的一个示例,大家感受下:
https://codepen.io/gaearon/pe...
最后(微信群)
这篇文章来的有点慢,非常抱歉。
另外为了方便大家阅读,我将所有文章的链接更新到第一篇文章 《玩转React(一)- 前言》 中。
文字的表现范围毕竟有限,为了方便大家交流,我建了一个微信群,对 React 感兴趣的同学可以进群一起交流、学习,由于微信群邀请的时间限制,大家可以先扫描下面二维码,加我好友,我拉大家进群:
我的微信:leobaba88
玩转 React(五)- 组件的内部状态和生命周期的更多相关文章
- react入门-组件方法、数据和生命周期
react组件也像vue一样,有data和methods,但是写法就很不同了: <!DOCTYPE html> <html lang="en"> <h ...
- 组件的详细说明和生命周期ComponentSpecs and Lifecycle
render ReactComponent render() render() 方法是必须的. 当调用的时候,会检测 this.props 和 this.state,返回一个单子级组件.该子级组件可以 ...
- k8s的Pod状态和生命周期管理
Pod状态和生命周期管理 一.什么是Pod? 二.Pod中如何管理多个容器? 三.使用Pod 四.Pod的持久性和终止 五.Pause容器 六.init容器 七.Pod的生命周期 (1)Pod p ...
- [转]Java 对象锁-synchronized()与线程的状态与生命周期
线程的状态与生命周期 Java 对象锁-synchronized() ? 1 2 3 4 synchronized(someObject){ //对象锁 } 对象锁的使用说明: 1.对象锁的返 ...
- react篇章-React State(状态)-将生命周期方法添加到类中
将生命周期方法添加到类中 在具有许多组件的应用程序中,在销毁时释放组件所占用的资源非常重要. 每当 Clock 组件第一次加载到 DOM 中的时候,我们都想生成定时器,这在 React 中被称为挂载. ...
- (五)Kubernetes Pod状态和生命周期管理
什么是Pod Pod是kubernetes中你可以创建和部署的最小也是最简的单位.Pod代表着集群中运行的进程. Pod中封装着应用的容器(有的情况下是好几个容器),存储.独立的网络IP,管理容器如何 ...
- React躬行记(4)——生命周期
组件的生命周期(Life Cycle)包含三个阶段:挂载(Mounting).更新(Updating)和卸载(Unmounting),在每个阶段都会有相应的回调方法(也叫钩子)可供选择,从而能更好的控 ...
- Kubernetes学习之路(十一)之Pod状态和生命周期管理
一.什么是Pod? Pod是kubernetes中你可以创建和部署的最小也是最简的单位.一个Pod代表着集群中运行的一个进程. Pod中封装着应用的容器(有的情况下是好几个容器),存储.独立的网络IP ...
- Pod——状态和生命周期管理及探针和资源限制
一.什么是Podkubernetes中的一切都可以理解为是一种资源对象,pod,rc,service,都可以理解是 一种资源对象.pod的组成示意图如下,由一个叫”pause“的根容器,加上一个或多个 ...
随机推荐
- Graylog
Graylog #Graylog 是与 ELK 可以相提并论的一款集中式日志管理方案,支持数据收集.检索.可视化 #Graylog 架构 - Graylog 负责接收来自各种设备和应用的日志,并为用 ...
- Linux_切换,创建,和删除目录
切换目录 cd /tmp cd ..切换到上一个目录 cd ~ 进入当前用户的家目录 cd ~nbzyh 进入zyh用户 创建目录 当当前在tmp中时:mkdir /tmp/sam 这里用的是绝对 ...
- The equal-likelihood model|event|experiment|probability model
5.1Probability Basics uncertainty is inherent in inferential statistics,因为总是需要样本估计总体,The science of ...
- Java面试题1-附答案
List和Set比较,各自的子类比较 对比一:Arraylist与LinkedList的比较 1.ArrayList是实现了基于动态数组的数据结构,因为地址连续,一旦数据存储好了,查询操作效率会比较高 ...
- 允许外部访问Windows本机的指定端口
背景:目前公司有一台公网Windows服务器,有公网IP和内网IP,防火墙已开启 需求:9999端口需要对外开放 方案:在防火墙的入站规则里添加一条规则,使外部能够访问9999端口 问题:添加好规则后 ...
- 结构体struct,类class
1.struct,值类型,结构体会自动生成初始化方法,class是引用类型 struct Person { var name : String var age : Int func simpleDes ...
- iOS 中UIWebView的cookie
有关cookie是什么,大家可以自行百度,本文我获得cookie的目的是得到一个userID. 下面的是代码. - (void)getUserIDFromCookie { NSHTTPCookieSt ...
- python en(de)code
python爬虫 代码写挺长的,也是边学边写,但一直搞不清楚python的encode(编码)和decode(解码).以下是我的探究之路. 一.当然先看官方文档 地址如下 里面提到encode函数'R ...
- @EnableWebMvc WebMvcConfigurer CorsConfig
package me.zhengjie.core.config; import org.springframework.context.annotation.Configuration; import ...
- VisionPro和Halcon的详细对比
一.概括的对比 1.1 Halcon的优势 Halcon有着更加低廉的Lisence 1.并且提供更好.更强大的2D和3D的视觉软件库 2.Halcon支持的视觉图像采集设备数量是Visionpro ...