文章标题总算是可以正常一点了……

通过之前的文章我们已经知道:在 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)。

上面例子中有用到 componentDidMountcomponentWillUnmount 两个函数,它们是组件的生命周期函数,本文的后半部分将会介绍,这俩函数分别在组件挂载到页面上和组件将要从页面上移除时调用。

改造后的例子,我们只需要调用一次 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。另外还有 setIntervalsetTimeout 等原生 API 的回调函数也是如此。

参考:https://www.zhihu.com/questio...

不要直接通过 this.state 来更新组件状态

这一点跟属性类似,直接通过 this.state 修改组件状态,组件状态被修改了,但并不会触发组件的重新渲染。这样就会导致组件视图与状态不一致。

生命周期函数

一个组件被我们创造到这个世界上之后,在使用它时,它的每个实例都是有一定生命周期的,下面这张图说明了一个组件实例的生命周期:

图片来源:https://tylermcginnis.com/an-...,这张图略微有点老,不过结合下文来看也没什么问题。

下面我们来解释一下上面这张图。

组件初始化:constructor

我们定义的每一个组件,都是一个类(class),这些类被实例化后才能作为 React DOM 中的一个节点渲染到页面上。所以,当我们通过 ReactDOM.render 或者在某个组件中通过 JSX 表达式将一个组件第一次渲染到页面上时,组件首先要做的就是对组件进行实例化。

实例化主要做的事情:

  • 创建一个组件的实例对象(也就是 Element,通常对应一个JSX表达式,如:<MyComponent />)。

  • 获取组件的默认属性。

  • 获取组件的初始内部状态(在 constructorthis.state = xxxx;)。

componentWillMount

在组件被渲染到页面上之前执行,在组件的整个生命周期内只执行一次。在这里可以调用 setState 更新内部状态,但是更推荐将这里的状态更新操作放到 constructor 中。

该函数执行完后会立马执行 render 方法并将组件渲染到页面上。所以,在这里执行 setState 不会触发额外的渲染过程,因为这是没有必要的。

componentDidMount

组件被渲染到页面上后立马执行,在组件的整个生命周期内只执行一次。这个时候是做如下操作的好时机:

  • 某些依赖组件 DOM 节点的操作。

  • 发起网络请求。

  • 设置 setIntervalsetTimeout 等计时器操作。

在这里可以调用 setState 更新组件内部状态,且会触发一个重新渲染的过程,即会重新执行 render 方法并更新视图。

componentWillReceiveProps

componentWillReceiveProps(nextProps)

该声明周期函数可能在两种情况下被调用:

  1. 组件接收到了新的属性。新的属性会通过 nextProps 获取到。

  2. 组件没有收到新的属性,但是由于父组件重新渲染导致当前组件也被重新渲染。

你只要知道,当该函数被调用时,并不一定是因为属性发生了变化

在这里也可以调用 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(五)- 组件的内部状态和生命周期的更多相关文章

  1. react入门-组件方法、数据和生命周期

    react组件也像vue一样,有data和methods,但是写法就很不同了: <!DOCTYPE html> <html lang="en"> <h ...

  2. 组件的详细说明和生命周期ComponentSpecs and Lifecycle

    render ReactComponent render() render() 方法是必须的. 当调用的时候,会检测 this.props 和 this.state,返回一个单子级组件.该子级组件可以 ...

  3. k8s的Pod状态和生命周期管理

    Pod状态和生命周期管理   一.什么是Pod? 二.Pod中如何管理多个容器? 三.使用Pod 四.Pod的持久性和终止 五.Pause容器 六.init容器 七.Pod的生命周期 (1)Pod p ...

  4. [转]Java 对象锁-synchronized()与线程的状态与生命周期

      线程的状态与生命周期 Java 对象锁-synchronized() ? 1 2 3 4 synchronized(someObject){   //对象锁 } 对象锁的使用说明: 1.对象锁的返 ...

  5. react篇章-React State(状态)-将生命周期方法添加到类中

    将生命周期方法添加到类中 在具有许多组件的应用程序中,在销毁时释放组件所占用的资源非常重要. 每当 Clock 组件第一次加载到 DOM 中的时候,我们都想生成定时器,这在 React 中被称为挂载. ...

  6. (五)Kubernetes Pod状态和生命周期管理

    什么是Pod Pod是kubernetes中你可以创建和部署的最小也是最简的单位.Pod代表着集群中运行的进程. Pod中封装着应用的容器(有的情况下是好几个容器),存储.独立的网络IP,管理容器如何 ...

  7. React躬行记(4)——生命周期

    组件的生命周期(Life Cycle)包含三个阶段:挂载(Mounting).更新(Updating)和卸载(Unmounting),在每个阶段都会有相应的回调方法(也叫钩子)可供选择,从而能更好的控 ...

  8. Kubernetes学习之路(十一)之Pod状态和生命周期管理

    一.什么是Pod? Pod是kubernetes中你可以创建和部署的最小也是最简的单位.一个Pod代表着集群中运行的一个进程. Pod中封装着应用的容器(有的情况下是好几个容器),存储.独立的网络IP ...

  9. Pod——状态和生命周期管理及探针和资源限制

    一.什么是Podkubernetes中的一切都可以理解为是一种资源对象,pod,rc,service,都可以理解是 一种资源对象.pod的组成示意图如下,由一个叫”pause“的根容器,加上一个或多个 ...

随机推荐

  1. xianduanshu

    https://www.cnblogs.com/xenny/p/9739600.html ***************https://blog.csdn.net/shiqi_614/article/ ...

  2. PAT甲级——1058 A+B in Hogwarts

    1058 A+B in Hogwarts If you are a fan of Harry Potter, you would know the world of magic has its own ...

  3. PAT甲级——1041 Be Unique

    1041 Be Unique Being unique is so important to people on Mars that even their lottery is designed in ...

  4. AppCompatActivity 透明背景

    <!-- 背景透明样式 --> <style name="AppTheme.transparent" parent="Theme.AppCompat.L ...

  5. OfficeCommandbarViewer20171005.rar

    OfficeCommandbarViewer用于查看Office各组件工具栏和控件信息的一款软件. 采用了本地XML文件的方式,所以使用本工具不需要提前打开任何Office组件. 动态图: 下载地址: ...

  6. 3.redis kyes命令

    Keys命令 1.1设置key的生存时间 Redis在实际使用过程中更多的用作缓存,然而缓存的数据一般都是需要设置生存时间的,即:到期后数据销毁. EXPIRE key seconds         ...

  7. [LC] 15. 3Sum

    Given an array nums of n integers, are there elements a, b, c in nums such that a + b + c = 0? Find ...

  8. python语法基础-基础-运算符

    ############################################ Python语言支持以下类型的运算符: 算术运算符 比较(关系)运算符 赋值运算符 逻辑运算符 位运算符 成员 ...

  9. python语法基础-基础-变量和数据类型

    ###############   第一个python程序   ############### print("hello python") # 打印hello python # 分 ...

  10. javasc-正则表达式

    匹配中文字符的正则表达式: [\u4e00-\u9fa5]评注:匹配中文还真是个头疼的事,有了这个表达式就好办了 匹配双字节字符(包括汉字在内):[^\x00-\xff]评注:可以用来计算字符串的长度 ...